Objective-Cでコマンドの中身を作るまで
前回までに、コマンドの雛形は出来上がった。あとは価値ある中身を作るだけ。
そういえば、AppleScriptで音を鳴らすのはbeepしか知らない。beepではシステム環境設定の警告音に設定した音しか鳴らせない。その他の警告音を鳴らすには、システム環境設定の警告音を変更するしかない...。もっと自由に警告音を活用したいと常々思っていた。警告音を自由に鳴らせるコマンドを作れば、それを簡単にAppleScriptから利用できる。
そうだ、soundコマンドを作ってみよう!
- 幸い自分のMacBookには、まだsoundというコマンドは存在しない。コマンド名としてsoundが使えるのだ。
$ sound -bash: sound: command not found
- afplayコマンド、ありました!(soundコマンド作るまでもなく)
$ afplay /System/Library/Sounds/Submarine.aiff
Cocoaを利用する
NSSound
そして調べてみると...
Cocoaには NSSound が用意されていて、これを使うとたった2行で音が出せる。
NSSound *sound = [NSSound soundNamed:@"Submarine"]; [sound play];
Cocoaの日々
求めていたものがここにあった!
Xcodeで試す
- 早速Xcodeで、指定した音を鳴らすアプリケーションを作って試してみた。
- 新規プロジェクト... >> Mac OSXのApplication >> Cocoa Application を選択して、[選択...]ボタンを押す。
- プロジェクト名は安易に「sound」とした。
- 開いたプロジェクトウィンドウの「グループとファイル」のClassesを選択して、soundAppDelegate.mを以下のように編集した。
- // Insert code here to initialize your application 以下に2行追加しただけ。
// // soundAppDelegate.m // sound // // Created by zari on 10/02/04. // Copyright 2010 __MyCompanyName__. All rights reserved. // #import "soundAppDelegate.h" @implementation soundAppDelegate @synthesize window; - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { // Insert code here to initialize your application NSSound *sound = [NSSound soundNamed:@"Submarine"]; [sound play]; } @end
- 追記したら、すかさず、ツールバーの[ビルドと実行]ボタンを押す。
- 空白のウィンドウが開き、「フォ〜ン」と警告音Submarineが鳴った!(たったこれだけでも成功すると嬉しい)
Cocoaを利用する雛形
- 実験用のGUIアプリケーションとしては上記の作り方で問題ないのだが、今欲しいのは、main()でコマンドが処理する過程でNSSoundを利用したい。
- 前回作ったhelloコマンドに単純に2行追加したくなるが...
#include <stdio.h> int main (int argc, const char * argv[]) { NSSound *sound = [NSSound soundNamed:@"Submarine"]; [sound play]; // insert code here... printf("Hello, World!\n"); return 0; }
- それだけではCocoaの機能は使えないらしい。ビルドするとエラー出まくり...。
Command Line ToolプロジェクトでCocoa環境を利用するには、以下の手順が必要だった。
- ファイル名を変更
- main.c → main.mに変更した。(main.cを選択して、右クリック、名称変更)
- 拡張子.cは、C言語と解釈される。
- 拡張子.mは、Objective-cと解釈される。
- Cocoa.frameworkの追加
- そして、main.mに以下の追記をした。
- Cocoa.frameworkのインポート //__(1)__
- NSAutoreleasePoolによるメモリ管理 //__(2)__
#include <stdio.h> #import <Cocoa/Cocoa.h> //__(1)__ int main (int argc, char * argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; //__(2)__ NSSound *sound = [NSSound soundNamed:@"Submarine"]; [sound play]; // insert code here... printf("Hello, World!\n"); [pool release]; //__(2)__ return 0; }
- [ビルドと実行]ボタンを押してみる。
...(中略)... run [Switching to process 10091] 実行中... Hello, World!
- 問題なく完了しましたと表示された。
- しかし、警告音が鳴らない...。なぜだろう?
- Hello, World!は表示されているので、処理は正常に進んでいると思うのだが...。
- 暫し悩んで気付いたのが、NSSoundオブジェクトが生成されて即、リリース(解放)されてしまっていること。
- GUIアプリケーションとして作った時は、アプリケーションはメニューの終了を実行するまで起動していた。
- その間、NSSoundオブジェクトは保持されているのかもしれない。
- sleep(1);を追記して、終了まで1秒間の猶予を与えてみた。
#include <stdio.h> #import <Cocoa/Cocoa.h> int main (int argc, char * argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; NSSound *sound = [NSSound soundNamed:@"Submarine"]; [sound play]; sleep(1); // 秒単位で処理を停止する // insert code here... printf("Hello, World!\n"); [pool release]; return 0; }
- [ビルドと実行]ボタンを押してみる。
...フォ〜ン
- 警告音が鳴った!成功だ。
コマンドの雛形に組み込む
- まだ、soundコマンドのオプション仕様は考えていないが、引数に警告音の名前を取ることだけ想定して作ってみる。
- 雛形として残すために、無意味な-a --abcオプションのみ解析するようにしてみた。
#include <stdio.h> /* printf() */ #include <stdlib.h> /* exit() */ #include <unistd.h> /* ?????? */ #include <getopt.h> /* getopt_long() */ #import <Cocoa/Cocoa.h> int main(int argc, char * argv[]){ NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; int opt, option_index; struct option long_options[] = { // {"name1", no_argument, NULL, 'n' } // {"name2", required_argument, &flag, "name2"} // {"name3", optional_argument, &flag, "name3"} {"abc", no_argument, NULL, 'a'}, {0, 0, 0, 0} // 配列の最後はすべて0で埋める }; //opterr = 0;/* getopt側のエラーメッセージを非表示にする */ while((opt = getopt_long_only(argc, argv, "a", long_options, &option_index)) != -1){ switch(opt){ case 'a': printf("Option -%c = %s\n", opt, optarg); break; // 解析できないオプションが見つかった場合は「?」を返す // オプション引数が不足している場合も「?」を返す case '?': //printf("Unknown or required argument option -%c\n", optopt); printf("Usage: COMMAND [-m | -e] [-s suffix] name ...\n"); return 1; // exit(EXIT_FAILURE);と同等 http://okwave.jp/qa/q794746.html } } NSString *name = [NSString stringWithCString: argv[optind] encoding: NSUTF8StringEncoding]; //printf("%s, playing...\n", argv[optind]); NSLog(@"%@, playing %d", name, i); NSSound *sound = [NSSound soundNamed: name]; [sound play]; sleep(1); // 秒単位で処理を停止する [pool release]; return 0; // exit(EXIT_SUCCESS);と同等 http://okwave.jp/qa/q794746.html }
- コンパイルして、実行してみる。
$ gcc sound.m -o sound -framework cocoa $ ./sound -abc Blow Option -a = (null) 2010-02-10 05:59:02.852 sound[12221:903] Blow, playing...
これで、引数に警告音の名前を指定できるようになった!
もう少し洗練させる
- -l --loopオプションを設定して、繰り返し回数を指定できるようにしてみた。
- 音楽ファイルのパスも指定できるようにしてみた。
- その際、再生中かどうかisPlayingで確認するようにした。
- そうしないと、すべての演奏時間が1秒になってしまう...。
- でも、使い方によってはイントロクイズみたいで面白いかも。
- -v --verboseオプションも設定して、指定した時だけNSLog()を出力するようにしてみた。
#include <stdio.h> /* printf() */ #include <stdlib.h> /* exit() */ #include <unistd.h> /* ?????? */ #include <getopt.h> /* getopt_long() */ #import <Cocoa/Cocoa.h> int main(int argc, char * argv[]){ NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; int i, loop = 1; BOOL isVerbose; int opt, option_index; struct option long_options[] = { // {"name1", no_argument, NULL, 'n' } // {"name2", required_argument, &flag, "name1"} // {"name3", optional_argument, &flag, "name2"} {"loop", required_argument, NULL, 'l'}, {"verbose", no_argument, NULL, 'v'}, {0, 0, 0, 0} // 配列の最後はすべて0で埋める }; //opterr = 0; // getopt側のエラーメッセージを非表示にする while((opt = getopt_long_only(argc, argv, "l:v", long_options, &option_index)) != -1){ switch(opt){ case 'l': //printf("Option -%c = %s\n", opt, optarg); loop = atoi(optarg); break; case 'v': //printf("Option -%c = %s\n", opt, optarg); isVerbose = TRUE; break; // 解析できないオプションが見つかった場合は「?」を返す // オプション引数が不足している場合も「?」を返す case '?': //printf("Unknown or required argument option -%c\n", optopt); printf("Usage: COMMAND [[-l|--loop] num] [-v|--verbose] name_or_path\n"); return 1; // exit(EXIT_FAILURE);と同等 http://okwave.jp/qa/q794746.html } } NSString *name_or_path = [NSString stringWithCString: argv[optind] encoding: NSUTF8StringEncoding]; NSSound *sound = [NSSound soundNamed: name_or_path]; if (sound == nil) { sound = [[[NSSound alloc] initWithContentsOfFile: name_or_path byReference: YES] autorelease]; } for (i = 0; i < loop; i++) { BOOL result; [sound setCurrentTime: 0]; result = [sound play]; if (isVerbose && result) { //printf("%s, playing...\n", argv[optind]); NSLog(@"%@ (%d), playing...", name_or_path, i); } while ([sound isPlaying]) { //sleep(1); // 秒単位で処理を停止する usleep(100); // ミリ秒単位で処理を停止する } if (isVerbose) { //printf("%s, stopped\n", argv[optind]); NSLog(@"%@ (%d), stopped", name_or_path, i); } } [pool release]; return 0; // exit(EXIT_SUCCESS);と同等 http://okwave.jp/qa/q794746.html }
- コンパイルして、試してみると...
$ gcc sound.m -o sound -framework cocoa $ ./sound -loop 2 Purr $ ./sound -loop 2 -v Purr 2010-02-10 5:22:55.699 sound[14380:903] Purr (0), playing... 2010-02-10 5:22:56.702 sound[14380:903] Purr (0), stopped 2010-02-10 5:22:56.703 sound[14380:903] Purr (1), playing... 2010-02-10 5:22:57.704 sound[14380:903] Purr (1), stopped $ ./sound -v /System/Library/Sounds/Sosumi.aiff 2010-02-10 5:24:26.908 sound[14395:903] /System/Library/Sounds/Sosumi.aiff (0), playing... 2010-02-10 5:24:27.911 sound[14395:903] /System/Library/Sounds/Sosumi.aiff (0), stopped
- いい感じで動いているみたい。大分コマンドらしくなった!
- パスを指定すれば、iTunesライブラリの音楽ファイルだって再生できるのだ。
ひとまず、soundコマンドの出来上がり!
NSSoundの仕様
- NSSoundの解説は、以下のページにあった。(英語)
- でも、やはり日本語で読みたい。(感謝です!)
参考ページ
以下のページが大変参考になりました。感謝です!