NikaLog

プログラムと音楽と、時々デザイン。

2012-01-02

旧友に会った話。

チラ裏ということで。

去年の暮に高校の同窓会があったのだけど、自分はきっとお呼びでないと思って参加はせず。で、同窓会があった話を旧友経由で聞いてみたり、そんな1月2日でした。

高校は地元の進学校で、思えばそこには入れたのは運が良かっただけで、高校では自分はあまりぱっとしなかったなあ。勉強が嫌いだったので…。

高校時代といえば、生徒会でもないのに生徒会室に行ってたり、国際科でもないのに国際科職員室によく行ってました。高校のWebページを勝手に作ってたり、応援団の曲を勝手に編曲したり、そういう日々でした。あとは勉強が多かったなあ、という感じ。良い思い出も色々あるのだけど、人付き合いが少し苦手で、高校時代の知り合いに会うことは殆どなくなってしまったかなあ。

高校、というか小学校の時からそうだったのですが、自分はPCとの付き合いが長くて、小学校の時にはちょっと良いペイントソフトでデザインをしたり、中学校の時にはDTMっぽいことも初めていました。高校では、音楽の宿題で先述したような曲の編曲をしたり、PCでデザインした自由研究的なものも作ったりしていました。それこそPCを絡めていろんなことをしていました。

今でも、デザインや音楽という面では、ライブ活動をしたり、作曲をしてニコニコ動画にアップしたり、ジャケットを作ったり、ウェブサイトを作ったりと続いているのですが、高校時代に軽音楽部に入っておけばよかったなあなんて思ったりもしました。

高校時代にDTMに理解のある友人に出会えれば、また人生も変わっていたのかもしれないのですが、残念ながら見つけることができませんでした。なので、DTMって本格的に始めたのは大学からなのです。

今は、中高生で曲を作る人もいれば、プログラミング能力に長けている人もいて、今の時代に生まれてたら、もっと違うことができていたのかもしれないのですが、とはいえ、過去のことを今言っても仕方が無いわけで。

こんな話を旧友と会って話していました。また来年も会うかもしれないので、過去の話ではなく、現在の話でネタになるものが出来ればなあと、そんなことを思ったりしました。

2011-12-13

Androidで波形再生

iPhone/iPadに比べて、Androidで音楽を作るソフトというのはあまり多くありません。自分もAndroidのプログラムを書いたりしていますが、Androidで事前に用意したMP3を鳴らすとかそういうのは出来ても、リアルタイムに波形を作って、それを出力する方法というのはあまり聞きません。ということでちょっと調べてみたところ、AudioTrackというオブジェクトを使うと、PCMを出力出来そうなことが分かりました。

PCMとは、アナログの波形をデジタルに変化する手法の一つで、おそらく最もナイーブな手法です。PCMは時間を細かく区切って、ある時間における波の高低を数値化することで、アナログの波形を数値の列に変換しています。CDもこの方式でアナログデータをデジタル化していて、CDの場合は1秒間を44100等分して、波の高低を216段階、すなわち65536段階のどれにあたるかを記録しています。

AndroidのAudioTrackは、byte型もしくはshort型の配列に数値を代入し、それを再生するというAPIです。例えば、配列を44100個用意して、正弦関数を使用して計算した値を代入し、その配列を1秒間鳴らせば、それは正弦波として聞くことができます。

AudioTrackのAPIについては、次のサイトが詳しいです。

とりあえずは、何かしら音声を数値化したものを用意すれば良いということです。音の波形の中で一番簡単な波は正弦波でしょう。正弦波の例を下に示してみます。

f:id:croneco:20111213231138j:image

青い波が「ド」の音、赤い波が「ミ」の音、黄色い波が「ソ」の音の波形をそれぞれ示しています。そして、緑の波が3つの波の平均を取ったものです。理論的には緑の波を聞けば、人間はその音がドとミとソの音を合わせた音、つまりCメジャーコードを聞くことになります。

緑の波だけを抽出したものを下に示します。

f:id:croneco:20111213231137j:image

なんだか複雑な形をしていますが、これがCメジャーコードの波形です。これは3つの正弦関数を純粋に平均しているだけなので、振幅が-1から1になっていますが、Androidでこの波形を鳴らす場合には、振幅をbyte型もしくはshort型が取り得る値の範囲にまで拡大する必要があります。実際にこれをAndroidで鳴らすとちゃんとCメジャーコードとして聞くことが出来ました。理論的には当たり前の結果なのですが、でもこういう複雑な波形をみてそれを聞くと、調和のとれた音として聞こえるのは不思議なものです。

今回作成したサンプルコードを下に掲載します。ほとんどにュウさいと様のサンプルなのですが、onResume関数やonPause関数を含め、ひと通りAndroidのプログラムの格好をしているものです。実際にはボタンを1つ配置し、それをクリックするとCメジャーコードを再生するプログラムとなっています。

package com.kuronekoya.test.at01;

import android.app.Activity;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class At01Activity extends Activity implements OnClickListener {
  Button btnPlay;
  AudioTrack track=null;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    btnPlay = (Button)findViewById(R.id.btnPlay);
    btnPlay.setOnClickListener(this);
  }

  @Override
  public void onResume(){
    super.onResume();
    if(track==null){
      track = new AudioTrack(
        AudioManager.STREAM_MUSIC,
        44100,
        AudioFormat.CHANNEL_CONFIGURATION_DEFAULT,
        AudioFormat.ENCODING_DEFAULT,
        44100,
        AudioTrack.MODE_STATIC);
    }

    // sine wave
    byte[] sinWave = new byte[44100];

    double freq_c3 = 261.6256;
    double freq_e3 = 329.6276;
    double freq_g3 = 391.9954;
    double t = 0.0;
    double dt = 1.0 / 44100;

    for (int i = 0; i < sinWave.length; i++, t += dt) {
      double sum = Math.sin(2.0 * Math.PI * t * freq_c3)
        + Math.sin(2.0 * Math.PI * t * freq_e3)
        + Math.sin(2.0 * Math.PI * t * freq_g3);
      sinWave[i] = (byte) (Byte.MAX_VALUE * (sum/3));
    }
    track.write(sinWave, 0, sinWave.length);
  }

  @Override
  public void onPause(){
    super.onPause();
    track.stop();
    track.release();
  }

  public void onClick(View v) {
    if(v==btnPlay){
      if(track.getPlayState()==android.media.AudioTrack.PLAYSTATE_PLAYING){
        track.stop();
        track.reloadStaticData();
      }
    track.play();
    }
  }
}

ちなみにWindowsなどで使われているWaveファイルはPCMによって変換した数値をそのままファイルにしているというシンプルな構造なので、AudioTrackで再生するのことは、特に難しいことをしなくても出来ます。

2011-12-12

MySQL Workbench

ER図を書こうとした時、これといって決め手となるソフトがないのです…。Eclipseプラグインもひとつの選択肢としてはあるのですが、個人的にはちょっとしっくり来ないなあというのが正直なところでした。

で、探してみたら、MySQL Workbenchというソフトを発見。このソフトを少し触ってみたのですが、これがなかなか良いのです。

f:id:croneco:20111212230906j:image

MySQLという名前の通り、MySQLサーバに接続してテーブルの構造やデータを見ることができます。これまでMySQLの操作には、phpMyAdminをよく使っていましたが、MySQL WorkbenchはWebアプリケーションではなく、OS上で動くソフトウェアなので、phpMyAdminよりもさらに直感的に使用出来ます。画面も今風で綺麗ですよね。

作ったER図はSQLでエクスポートできるし、画面左上のBird's Eyeを見ながら印刷するときの調整もできます。単なるモデリングだけではなく紙に印刷することまで考えられているので、仕様書を書くときにも十分使えます。

このソフトを今日知ったばかりなので、このソフトの奥深さがまだわかりません。メニューのPluginsとか気になりますし、Web上の情報を見ながら、使いこなせるようにしていきたいなあと思った今日この頃でした。

2011-12-10

Zeemoteを使ったAndroidのプログラムを組んでみました

Google Developer Day 2011にてZeemoteというデバイスを配っていました。ZeemoteというのはAndroid用のbluetoothのコントローラで、動かした様子は下のような感じ。

GAPSIS.JP様の映像を引用…

GDD2011から1ヶ月くらい放置していたのですが、ちょっと時間ができたので、少しいじって見ました。

とりあえず触ってみる

なにはともあれ、ZeemoteとAndroidをペアリングしなければ動かすことはできません。ペアリングは、[設定]-[無線とネットワーク]-[Bluetooth]にチェックを入れて、さらにその下の[Bluetoothの設定]から出来ます。

f:id:croneco:20111210020320j:image

Zeemoteの電源を入れて、付近のデバイスを検索します。

f:id:croneco:20111210020321j:image

見つかると、Zeemote JS1 Hというのがリストに表示されます。

f:id:croneco:20111210020322j:image

Zeemote JS1 Hを押して、PIN「0000」を入力します。

f:id:croneco:20111210020323j:image

そうすると、ペアリングされたデバイスにZeemote JS1 Hが表示されます。

f:id:croneco:20111210020324j:image

Zeemoteのページを見ると、R-TYPEというゲームでZeemoteが使えるみたいなので、早速ダウンロードしてプレイすると…、おお、確かにZeemoteで動く。

SDKのダウンロード

http://www.zeemote.com/にアクセスすると、右上に「DOWNLOAD ZDK」というリンクがあります。押してみましょう。

f:id:croneco:20111210013928j:image

すると、開発者ページが現れます。SDKをダウンロードするためには開発者登録が必要なので、必要事項を記入して、登録をしてください。

登録ができたら、開発者ページからログインをすることで、SDKダウンロードページに行くことができます。

Androidで開発をしようと思うので、Zeemote Android SDK 1.6.0をダウンロードします。

f:id:croneco:20111210013929j:image

ダウンロードしたzipファイルを展開するとこんな感じ。

f:id:croneco:20111210013930j:image

早速、Eclipseを起動して、examplesの下のandroid-sampleをインポートしてみます。

f:id:croneco:20111210013931j:image

そして、実行。Zeemoteのボタンやスティックを動かしていると、なんか表示されています。

f:id:croneco:20111209181544j:image

表示はこんな感じ。スティックの傾きや、ボタンのPress、Releaseが表示されています。

f:id:croneco:20111210021010j:image

オリジナルのプログラムを作成する

サンプルのソースを見てみると、案外難しくなさそうに見えます。buttonPressed関数はボタンが押されたときに呼ばれ、buttonReleased関数はボタンが離されたときに呼ばれます。2つの関数はButtonEventを引数に取りますが、ButtonEventにボタンの情報が格納されているのでしょう。joystickMoved関数も名前そのままで、スティックを動かしたときに呼ばれます。他にもconnected/disconnected関数があり、これらの関数は、IStatusListener, IJoystickListener, IButtonListenerを実装(Implements)する必要があるため、ソースコードに記述されています。

1からプログラムを作るときは、SDKに含まれているAndroid Programmer's Guideの5ページ "How To Use"に沿えば大丈夫だと思います。

まず、SDKのlibsフォルダをプロジェクトにコピーして、ビルドパスを通します。

また、bluetoothを使用するため、 android.permission.BLUETOOTHandroid.permission.BLUETOOTH_ADMIN の許可をAndroidManifest.xmlに書き込みます。

f:id:croneco:20111210022000j:image

そして、Zeemoteとの接続に必要なUIはライブラリが提供するものを使用したいため、com.zeemote.zc.ui.android.ControllerAndroidUi$Activity というアクティビティをアプリケーションから呼び出す設定をAndroidManifest.xmlに書き込みます。

f:id:croneco:20111210022301j:image

ソースコードはサンプルからコピペしてきて、必要な部分を編集するといいと思います。私はmp3ファイルを4つロードして、ボタンやスティックが操作されたら、アサインしたmp3を再生するというプログラムを作ってみました。

mp3の再生の部分を手を抜いているため、サウンドに重ねてサウンドを再生することができていませんが、一応音が鳴っているので、Zeemoteでの操作を受け取っていることが分かります。あとは、どんどん改造していけばいいですね。

下記に実際に作ったソースを掲載します。あまり良いコードではないのですが、参考になれば幸いです。

package hoge.zeemote.test2;

import java.io.IOException;

import com.zeemote.zc.Controller;
import com.zeemote.zc.event.BatteryEvent;
import com.zeemote.zc.event.ButtonEvent;
import com.zeemote.zc.event.ControllerEvent;
import com.zeemote.zc.event.DisconnectEvent;
import com.zeemote.zc.event.IButtonListener;
import com.zeemote.zc.event.IJoystickListener;
import com.zeemote.zc.event.IStatusListener;
import com.zeemote.zc.event.JoystickEvent;
import com.zeemote.zc.ui.android.ControllerAndroidUi;

import android.app.Activity;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.TextView;

public class Zeemote_test2Activity extends Activity implements IStatusListener,
IJoystickListener, IButtonListener {

  private static final int MENU_CONTROLLER = 1;
  private Controller controller;
  private ControllerAndroidUi controllerUi;
  private boolean controllerUiShown;
  public static final int GUIUPDATEIDENTIFIER = 0x101;
  private String info;

  MediaPlayer _hh;
  MediaPlayer _bd;
  MediaPlayer _sd;
  MediaPlayer _mac;

  TextView label;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

    controller = new Controller(1);
    controller.addStatusListener(this);
    controller.addButtonListener(this);
    controller.addJoystickListener(this);

    controllerUi = new ControllerAndroidUi(this, controller);
    controllerUiShown = false;

    _hh = MediaPlayer.create(this, R.raw.hh);
    _bd = MediaPlayer.create(this, R.raw.bd);
    _sd = MediaPlayer.create(this, R.raw.sd);
    _mac = MediaPlayer.create(this, R.raw.mac);

    label = (TextView)findViewById(R.id.lblText);
    label.setTextSize( (float) 120.0);
    }

  @Override
  public void onResume() {
    super.onResume();
    if (!controllerUiShown) {
      // show the controller activity.
      controllerUi.startConnectionProcess();
      controllerUiShown = true;
    } else {
      // the controller activity was hidden.
      controllerUiShown = false;
    }

    try {
      _hh.prepare();
      _bd.prepare();
      _sd.prepare();
      _mac.prepare();
    } catch (IllegalStateException e) {
      // TODO 自動生成された catch ブロック
      e.printStackTrace();
    } catch (IOException e) {
      // TODO 自動生成された catch ブロック
      e.printStackTrace();
    }
  }

  @Override
  public void onPause() {
    super.onPause();
    if (!controllerUiShown) {
      // If the controller activity is not shown,
      // disconnect the Zeemote controller.
      try {
        controller.disconnect();
      } catch (Exception e) {
      }
    }
  }

  /**
   * Invoked during init to give the Activity a chance to set up its Menu.
   *
   * @param menu
   *            the Menu to which entries may be added
   * @return true
   */
  @Override
  public boolean onCreateOptionsMenu(final Menu menu) {
    super.onCreateOptionsMenu(menu);

    menu.add(0, MENU_CONTROLLER, 0, "Controller");
    return true;
  }

  /**
   *
   */
  @Override
  public boolean onPrepareOptionsMenu(final Menu menu) {
    super.onPrepareOptionsMenu(menu);
    return true;
  }

  /**
   * Invoked when the user selects an item from the Menu.
   *
   * @param item
   *            the Menu entry which was selected
   * @return true if the Menu item was legit (and we consumed it), false
   *         otherwise
   */
  @Override
  public boolean onOptionsItemSelected(final MenuItem item) {
    switch (item.getItemId()) {
    case MENU_CONTROLLER:
      controllerUi.showControllerMenu();
      controllerUiShown = true;
      return true;
    }
    return false;
  }

  public void buttonPressed(ButtonEvent arg0) {
    // TODO 自動生成されたメソッド・スタブ
    // button id 0:A(bd), 1:B(sd), 2:C, 3:power
    int b = arg0.getButtonID();

    if(b==0){
      _bd.start();
      traceLog("BD");
    } else if(b==1){
      _sd.start();
      traceLog("SD");
    }

  }

  public void buttonReleased(ButtonEvent arg0) {
    // TODO 自動生成されたメソッド・スタブ

  }

  public void joystickMoved(JoystickEvent arg0) {
    // TODO 自動生成されたメソッド・スタブ
    int x = arg0.getScaledX(-100, 100);
    int y = arg0.getScaledY(-100, 100);

    if(x < -50){
      _hh.start();
      traceLog("HH");
    }

    if(x > 50){
      _mac.start();
      traceLog("MAC");
    }
  }


  public void batteryUpdate(BatteryEvent arg0) {
    // TODO 自動生成されたメソッド・スタブ

  }

  public void connected(ControllerEvent arg0) {
    // TODO 自動生成されたメソッド・スタブ

  }

  public void disconnected(DisconnectEvent arg0) {
    // TODO 自動生成されたメソッド・スタブ

  }

  protected void traceLog(final String s) {
    info = s;
    Message m = new Message();
    m.what = GUIUPDATEIDENTIFIER;
    this.ViewUpdateHandler.sendMessage(m);
  }

  Handler ViewUpdateHandler = new Handler() {

    public void handleMessage(Message msg) {
      switch (msg.what) {
      case GUIUPDATEIDENTIFIER:
        label.setText(info);
        break;
      }
      super.handleMessage(msg);
    }
  };
}
2011-11-27

[perl][mysql] PerlからMySQLに日本語を含むレコードを登録する際の文字化け対策

set namesを使うといいみたいです。

use strict;
use warnings;
use utf8;
use DBI;

my $dbh = DBI->connect($datasource, $username, $password);

# set namesを使う
$dbh->do('set names utf8');

$dbh->do('insert into SAMPLETABLE (`hoge`) values "ほげ"');

ただ、PHPの業界ではset namesを使用することはダメであることが言われています。WWWブラウザのフォームから受け取った、ユーザの入力に対して、PHPで処理する場合は、SET NAMESなどには脆弱性があるようです。

SET NAMESは禁止
http://blog.ohgaki.net/set_namesa_mcb_asc

SET NAMESが危険な理由のおさらい
http://www.bpsinc.jp/blog/archives/1133

SET NAMESは禁止?
http://cakephp.seesaa.net/article/52562968.html

PHPからSET NAMESを使わない方が良い理由と対策まとめ
http://nonn-et-twk.net/twk/why-set-names-in-php-is-bad

ただ、これらはPHPだけの問題なのか不明な上、そもそもこれらの話題が、現時点で2以上年前の話題であるため、もしかしたら、何か対策が打たれているか、そもそもset namesを使わないように技術書などに書かれているのかもしれません。

SQLインジェクションの対策は必要であることは念頭に入れたうえで、ユーザの入力の余地がない部分に対しては、とりあえず、set namesで解決できそうです。