井原プロダクトのBLOG

Since 2013。個人でアプリ作っています。

正確なメトロノームアプリの作成

以下は127bpmにおいて音を鳴らすべき時間の理論値と、実際に出すタイミングを比較してみたものです。

基本的なエンジン部分は、ほぼこちらの作りと同じで、実行結果は、Dataの通り誤差が1ms以内に収まるようになりました。0.002の差があるのに、結果が0.001というのは、1ms以下の数値が丸まっていると思われ。

http://stackoverflow.com/questions/4485072/accurate-timing-in-ios

2014-12-11 15:19:49.127 Metronome[5286:1146464] 理論=0.000 実際=0.000 差分=0.000
2014-12-11 15:19:49.685 Metronome[5286:1146464] 理論=0.529 実際=0.530 差分=0.001
2014-12-11 15:19:50.219 Metronome[5286:1146464] 理論=1.057 実際=1.059 差分=0.001
2014-12-11 15:19:50.753 Metronome[5286:1146464] 理論=1.586 実際=1.587 差分=0.001
2014-12-11 15:19:51.263 Metronome[5286:1146464] 理論=2.115 実際=2.115 差分=0.001
2014-12-11 15:19:51.797 Metronome[5286:1146464] 理論=2.643 実際=2.643 差分=0.000
2014-12-11 15:19:52.331 Metronome[5286:1146464] 理論=3.172 実際=3.173 差分=0.001
2014-12-11 15:19:52.865 Metronome[5286:1146464] 理論=3.700 実際=3.702 差分=0.001
2014-12-11 15:19:53.375 Metronome[5286:1146464] 理論=4.229 実際=4.230 差分=0.001
2014-12-11 15:19:53.909 Metronome[5286:1146464] 理論=4.758 実際=4.758 差分=0.000
2014-12-11 15:19:54.443 Metronome[5286:1146464] 理論=5.286 実際=5.288 差分=0.001
2014-12-11 15:19:54.978 Metronome[5286:1146464] 理論=5.815 実際=5.815 差分=0.000


だがしかし!

聞いた感じ、微妙に正確じゃないんです。これって人の感覚が1msを感知しているというより、オーディオ側のレスポンスが揺らいでるって事だよね orz

ということで、3種類試してみました。

  • AVAudioPlayerによる再生(これが今までの)
  • SystemSound Playerによる再生
  • OpenALを用いた再生

結果、、、、どれも同じ(爆)


おそらく別のアプローチが必要ですね。

メトロノームの正確性の追求

NSTimerを使うのは論外として、NSThreadを使ってタイマー処理を別にして、そのThreadのプライオリティーをあげて、というのが常套手段の様なのですが、それほど効果を感じられないです。

http://stackoverflow.com/questions/4485072/accurate-timing-in-ios

[NSThread setThreadPriority:1.0];

ってところがキモなのですが、これがあるのとないのとで、さほど変わりません。
超正確な「Metronome Star」とかどうやってるんだろう、、。

つづく

アプリ第4弾「練習カメラ」リリース

簡単操作の練習用カメラアプリができました!!

ピアノを練習していて、もっと簡単に録画再生ができないものか?といつも考えていました。iPhoneの標準のカメラを使うと「録画開始=>再生=>カメラに戻る」という一連の操作を行うには、6回の画面タップが必要です。録画の度にこれでは、練習の集中が途切れてしまいます。しかも、撮った動画がどんどん保存されてしまうので、後で消さないといけません。

そんな面倒を解決したい!というので生まれたのが「練習カメラ」。これは、同じ操作をするのに1回も画面をタップする必要がありません。操作はiPhoneに手をかざすだけ。

一度かざすと録画開始、もう一度かざすと録画停止、そして自動再生が始まります、終わったら手をかざして録画待機画面に。保存したいときだけ「保存」をタップする様になっています。これは切符がSUICAに変わった以来の大改革です(笑)。

無料ですので、iPhoneユーザの方はぜひお試しください。

また、アプリからピアノの横にiPhoneを設置できるスタンドをご購入いただけるようになっています。こちらもぜひよろしくお願いします。

技術的には、近接センサーを使っています。これ、ちょっと動作に癖があって、意外と実装には苦労しました。横向きでは動かないとか、Event発生にはON/OFFがあって、ONの状態で近接センサーをdisableにすると、次はOFFになったときしか検知しないとか。かといってずっとenableのままだと妙に感度が良くて、Video側の処理を待たずしてStatusが変わってダウンするとか、、。

それから、iPADには近接センサーがないため、同じ動作事はできないのですが、Appleの規定上、iPhoneアプリiPADでも動かないといけないため(実際、一度リジェクトされました。動かないアプリは結構あるんだけど、文句言っても仕方ない、、、)iPADでは手をかざすという動作ではなく、画面のどこかを触ってもらうことにしています。透明ボタンを画面いっぱいに配置しています。

また、UIImagePickerControllerで撮った動画は、明示的に消さないと消えません。これも想定外でした。ずっと保存されていくので、カメラ終了時、またアプリを落としたときなどのタイミングで、消去しないといけません。

@interface ViewController ()
NSString *pathToVideo;

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
    NSURL *videoURL  = [info valueForKey:UIImagePickerControllerMediaURL];
<span class="deco" style="color:#FF0000;">    pathToVideo = [videoURL path];
</span>    //PickerControllerを閉じる
    [self dismissViewControllerAnimated:YES completion:nil];

//   取った画像の再生
    NSURL *url = [info valueForKey:UIImagePickerControllerMediaURL];
    playerItem = [[AVPlayerItem alloc] initWithURL:url];
    [playerItem addObserver:self forKeyPath:@"status" options:0 context:&ItemStatusContext];
    [[NSNotificationCenter defaultCenter]
     addObserver:self
     selector:@selector(playerItemDidReachEnd:)
     name:AVPlayerItemDidPlayToEndTimeNotification
     object:playerItem];
    player = [AVPlayer playerWithPlayerItem:playerItem];
    [playerView setPlayer:player];
    //再生開始
    [player play];
}


-(void) deleteFile
    {
    //ここで録画したファイルを消す
    NSFileManager *fileManager = [NSFileManager defaultManager];
    if ([fileManager fileExistsAtPath:pathToVideo]) {
        NSError *error;
        // Attempt to delete the folder containing globalDel.videoPath
        
        if ([fileManager removeItemAtPath:[pathToVideo stringByDeletingLastPathComponent] error:&error] != YES)
        {
            NSLog(@"Unable to delete file: %@", [error localizedDescription]);
        }else{
            NSLog (@"消去成功");
        }
        
    } else {
        NSLog (@"消去失敗");
        
    }
}

アプリのデモ動画

no input file error

x-code 6.1 objective-c

Command /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang failed with exit code 1

というエラーが出て、

clang: error: no such file or directory: '/Users/hogehoge/hogehoge/....'
clang: error: no input files

などとあったのですが、確認するとhogehoge以下のファイルはある。

結局、Menu -> product -> clean をしたら直りました。なんだかなぁ、、。

NSTimerの正確性の検証

検証してみた。以下のプログラムで、NSDateによる時間経過のタイミングと、NSTimerによるタイマーのカウントアップ値の差分を比較。viewControllerをClassとするViewにSTART/STOPボタンを割り付けています。

NSDate : iPhoneの時計の経過により時間を計測
NSTimer : 1/1000に一回カウンターをカウントアップ

NSDate *stdate;
NSDate *datesave;
NSTimer *tm;
int counter;

- (void)viewDidLoad{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (void) timeMJ{
    counter ++;
}

- (IBAction)startButton:(UIButton *)sender {
    counter = 0;
    float interval;
    interval = 0.001;
    tm = [NSTimer
          scheduledTimerWithTimeInterval:interval
          target:self
          selector:@selector(timeMJ)
          userInfo:nil
          repeats:YES
          ];
    datesave = [NSDate date];
    [tm fire];
}
- (IBAction)stopButton:(UIButton *)sender {
    [tm invalidate];
    stdate = [NSDate date];
    float x = [stdate timeIntervalSinceDate:datesave];
    NSLog (@"Clock = %.0f Counter = %d 誤差=%.2f%", x*1000, counter,(x*1000-counter)/x/10);

}

iPhone5Sでの実行結果。

Clock = 1868 Counter = 1865 誤差=0.15%
Clock = 1866 Counter = 1862 誤差=0.19%
Clock = 1983 Counter = 1979 誤差=0.21%
Clock = 1967 Counter = 1963 誤差=0.18%
Clock = 1867 Counter = 1864 誤差=0.16%
Clock = 2283 Counter = 2280 誤差=0.14%
Clock = 1917 Counter = 1914 誤差=0.14%
Clock = 2668 Counter = 2657 誤差=0.41%
Clock = 3850 Counter = 3845 誤差=0.13%
Clock = 3217 Counter = 3212 誤差=0.14%
Clock = 2617 Counter = 2614 誤差=0.10%
Clock = 1867 Counter = 1864 誤差=0.14%
Clock = 2182 Counter = 2154 誤差=1.30%
Clock = 2449 Counter = 2445 誤差=0.16%
Clock = 2268 Counter = 2265 誤差=0.12%
Clock = 2050 Counter = 2046 誤差=0.21%
Clock = 2616 Counter = 2613 誤差=0.10%
Clock = 2100 Counter = 2097 誤差=0.15%
Clock = 2034 Counter = 2030 誤差=0.17%
Clock = 2249 Counter = 2103 誤差=6.50%
Clock = 2084 Counter = 2082 誤差=0.12%
Clock = 1767 Counter = 1765 誤差=0.11%

これじゃ、STOP Watchとしてはダメですね。

iPhone動画カメラアプリは、撮ったファイルが自動で消えない

今作ってる動画カメラアプリですが、iTUNES Connectで申請まで終わって、やれやれ、ということで自分のiPhoneにローカルインストールして使っていたら、ストレージがいっぱいになったぞゴルァ!と怒られてしまいました。んなこたーないだろう!?とiPhoneのストレージ使用状況を見ると、何と私のカメラアプリが、数ギガにまで膨れ上がってるじゃないか!?ということで早速調べてみた。

このアプリは、明示的に「保存」しない限り、写真フォルダーに動画を保存しないつくりなのですが、それでも一時ファイルがどんどん残ってしまいます。なので消さないといけなかったのね。早速、申請を取り下げて修正をすることに。

//インスタンス変数として宣言
NSString *pathToVideo;

//録画が終わったら起動されるメソッド
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
    NSURL *videoURL  = [info valueForKey:UIImagePickerControllerMediaURL];
    pathToVideo = [videoURL path];
    _videoURL = pathToVideo;
    //PickerControllerを閉じる
    [self dismissViewControllerAnimated:YES completion:nil];
    
//   取った画像の再生
    NSURL *url = [info valueForKey:UIImagePickerControllerMediaURL];
    playerItem = [[AVPlayerItem alloc] initWithURL:url];
    [playerItem addObserver:self forKeyPath:@"status" options:0 context:&ItemStatusContext];
    [[NSNotificationCenter defaultCenter]
     addObserver:self
     selector:@selector(playerItemDidReachEnd:)
     name:AVPlayerItemDidPlayToEndTimeNotification
     object:playerItem];
    player = [AVPlayer playerWithPlayerItem:playerItem];
    [playerView setPlayer:player];
    //再生開始
    [player play];
}


//必要なタイミングでこのメソッドを呼び出す
-(void) deleteFile
    {
    //ここで録画したファイルを消す
    NSFileManager *fileManager = [NSFileManager defaultManager];
    if ([fileManager fileExistsAtPath:pathToVideo]) {
        NSError *error;
        // Attempt to delete the folder containing globalDel.videoPath
        
        if ([fileManager removeItemAtPath:[pathToVideo stringByDeletingLastPathComponent] error:&error] != YES)
        {
            NSLog(@"Unable to delete file: %@", [error localizedDescription]);
        }else{
            NSLog (@"消去成功");
        }
    } else {
        NSLog (@"消去失敗");
        
    }
}

アプリ開発用にディスプレイを新調

x-codeを使ってアプリ開発していますが、今までの開発環境はRatinaになる前の13インチのMBPと、BENQの15.6インチの外付けディスプレイでした。これだと手狭なので、拡張することにしました。

iiyama 23インチ、XUB2390HS-B1 1968 X 1080 ピクセル

これでx-codeを使うとどうなるかというと、こんな感じ。至って快適。マルチスクリーンで、MBP側をメール、ネット検索、素材編集とかの作業画面にして、プログラミングは大画面側で。今までが1366X768なので、ピクセルの数でいうと約2倍になったわけです。


これだと、アシスタントエディタを開いた状態でも、ガンガンコードが書けますね。今日1日作業してみて、良い点と悪い点を。

良い点:
・これが一番の目的で9割を占めるのだけど、でかい。同時に色々な作業ができる
・反射が少ないので、非常に目が楽
・モニタから離れても(70cmくらい)コードが見える(私、強制視力0.9)。これは、モニタを見るために特定の姿勢を維持しなくて良いということになります。椅子の背もたれによっかかってコードが書ける。非常に楽、腰にも優しい。
・高さ調整ができるため、サブ画面との接地位置調整が可能。
・価格も23K弱というのは安い

残念だった点:
・パネルがもうちょっと低い位置まで下がると嬉しい。
・パネルを縦に回転する仕組みがあるのだが、この機能のせいで横に使っていても水平がずれやすい。水平ってちょっとずれるだけで気になるんですよ。なので水平箇所でカチっと止まると嬉しい。
・スピーカーがついているのだが、音はしょぼい、実にしょぼい。

Thunderbold-HDMI変換ケーブルでつないでいます。

尚、HDMI接続すると、自動的に音声出力もHDMI側に出力されて、MACのヘッドホン端子から音が出なくなります。これを直すには、システム環境設定=>サウンドで、「出力」タブを選んでサウンドを出力する装置を選択から「ヘッドホン」を選びます。

MBPと外付けという使い方の場合、キーボード&マウスも外付けをお勧めします。左右に並べた状態でMBPのキーボードを使うと首が疲れますので。

というわけで、もっと早く導入すればよかった。