NScripterでボイス作成

 もしかしたら、荘大な無駄な努力かも知れん。

このエントリーの最終目標

NScripterで、ボイスファイルをつなぎ合わせてその場でセリフを作成すること。
もっとわかりやすく言うならば、NScripter

のようなことをやれないかな、と言う話。

応用

方法論

 ボイスを作成すると言うが、つまるところ「こんにちは」と言うセリフを言わせるために、「こ.wav」「ん.wav」「に.wav」「ち.wav」「わ.wav」を順繰りに再生する、そういう仕組みを作るだけの話である。
 ただし、これをプログラム的に表現すると「こ.wavを再生する。それが終わればん.wavを再生する。それが終われば(以下略」となるが、問題になるのは「終われば」と言う、演奏の終了判定である。
 automode命令によるオートスキップから、NScripterの内部ではこの終了判定が可能なことは推定できるが、マニュアルを見る限りそれを開発者やユーザーが使用する方法はないようだ。(将来的にこれが可能になれば、このエントリーで書かれたことは全てゴミクズと化す。きゃほー)
 次善の策として、演奏したいファイルの長さを事前に取得することが考えられる。演奏開始からカウントしてそれと同じ時間が経過した時、実際の演奏状況に関係なく終了とみなして次の処理に進む訳だ。
 では、ファイルの演奏時間を取得する方法について考えてみるが、ボイスにあわせて文字速度を変える。 - 永字八法では、事前も事前、作成段階で取得しておくようにしていたが、正直それはどうなんよと言わざるを得ない。データの持ち方としてはあまり美しくもない訳だし、スクリプターにとっても負担が大きい。
 ので、ファイルから直接演奏時間を取得することを考えた。
 その結果、後述のvoice_len.exeが完成し、wavに限定してだが、その演奏時間をミリ秒単位で取得することに成功した。
 後は、これをNScripterからDLLを介さずに他の言語で作られたアプリを使う。 - 永字八法を応用してNScripterから扱えるようにして、サンプルプログラムを作成した。

サンプル

voice_len.exeの中身。

 Windowsのwavの仕様は見つけたので、それに沿ってファイルヘッダーを解析するプログラムをPerlで組んでみた。wavのファイルヘッダーの中に、「データ部分の長さ」と「一秒あたりのバイト数」があるので、前者を後者で割り、1,000倍すると演奏時間(ミリ秒)になる。端数は切り捨てた。
 それをNScripterからDLLを介さずに他の言語で作られたアプリを使う。 - 永字八法に適合するように整えて、ppでアプリ化して完成である。

open ( IN, 'command.txt' );
my @file = <IN>;
close ( IN );
map { chomp $_ } @file;
map { chomp $_ } @file;
open ( OUT, '>result.txt' );
if ( 0 == scalar @file ) {
	print OUT '';
	exit(0);
}

my $line = '';
my @type = qw (
	RIFF
	SIZE-8
	WAVE
	fmt
	16
	format
	channel
	SAMPLING
	Bit/Sec
	Byte/S
	Bit/S
);
my %hack = (
	'RIFF'=>'A4',
	'SIZE-8'=>'L',
	'WAVE'=>'A4',
	'fmt'=>'A4',
	'16'=>'L',
	'format'=>'S',
	'channel'=>'S',
	'SAMPLING'=>'L',
	'Bit/Sec'=>'L',
	'Byte/S'=>'S',
	'Bit/S'=>'S',
);
	my %data = ();
	my $str = '';
	map { $str .= $hack{$_} } @type;
	my @result = ();
	my $length = 0;
	my @output = ();

foreach my $file ( @file ) {
	$length = 0;
	if ( open ( IN, $file ) ) {
		binmode ( IN );
		read ( IN, $line, 36 );
		%data = ();

		@result = unpack $str, $line;
		map { $data{$_} = shift @result } @type;

		unless ( $data{'16'} == 16 ) {
			read ( IN, $line, 2 );
			my $count = unpack 'S', $line;
			$count and read ( IN, $line, $count );
		}

		read ( IN, $line, 8 );
		my ( $data_chunk, $data_size ) = unpack 'A4L', $line;
		$data{'DATA'} = $data_chunk;
		$data{'DATA_SIZE'} = $data_size;
		$length = int ( 1000 * $data{'DATA_SIZE'} / $data{'Bit/Sec'} );
		close ( IN );
	}
	push @output, '"'.$file.'",'.$length;
}
print OUT join ( "\n", @output );

00.txtの中身

 NScripter側は、このように出迎えた。

*start

voice_len %1,"JNGL001.wav"
voice_len %2,"JNGL002.wav"
voice_len %3,"JNGL003.wav"
voice_len %4,"JNGL004.wav"

ファイル1:%1ミリ秒
dwave 0,"JNGL001.wav"
resettimer
waittimer %1

ファイル2:%2ミリ秒
dwave 0,"JNGL002.wav"
resettimer
waittimer %2

ファイル3:%3ミリ秒
dwave 0,"JNGL003.wav"
resettimer
waittimer %3

ファイル4:%4ミリ秒
dwave 0,"JNGL004.wav"
resettimer
waittimer %4

演奏終了\
end
*define

mov %0,100
numalias voice_len_file,%0:inc %0
numalias voice_len_len,%0:inc %0
defsub voice_len

game

*voice_len
; パラメータの取得
getparam i%voice_len_len,$voice_len_file
; 対象のファイルの有無のチェック
mov %%voice_len_len,-1 ; 初期化
fileexist %voice_len_file,$voice_len_file
if %voice_len_file=0 return ; 存在しないなら何もしない。
; 前回の実行結果を削除。
fileremove "result.txt"
; 書き込み
csvopen "command.txt","w"
csvwrite $voice_len_file
csvclose
; 実行
winexec "voice_len.exe",0
; 結果が出来上がるまで待つ。
wait 10
fileexist %voice_len_file,"result.txt"
if %voice_len_file=0 skip -2
csvopen "result.txt","r"
csvread $voice_len_file,%%voice_len_len
csvclose
return

; wavファイルは、「音楽工房」よりコピーしています。
; http://www.ddnj.com/product/sound/ok/index.html

今後の課題

 でもまあ、こういう外部アプリの使い方は究極ダサいよね。やっぱdllを作って、execdll命令から使ってみたいよね。
 と、言う訳で、誰かこの仕組みをCとかC++で書きなおして、リプレースしてくれないかなと他力本願で終わる。

おまえはマイノリティなんだよ!

目算を誤る - 永字八法の続き。
100円ショップに空気読まずメルフォ書いた結果が出た。

お問合せの収納袋の件ですが、
あいにくA5サイズは文庫本やコミックなどと異なり
シリーズでの収納用途が少なく、取扱いがございません。

つまり、エロ漫画は日陰の本である、と。……おのれっ! マイノリティなめんな!

おまえはマイノリティなんだよ!

目算を誤る - 永字八法の続き。
100円ショップに空気読まずメルフォ書いた結果が出た。

お問合せの収納袋の件ですが、
あいにくA5サイズは文庫本やコミックなどと異なり
シリーズでの収納用途が少なく、取扱いがございません。

つまり、エロ漫画は日陰の本である、と。……おのれっ! マイノリティなめんな!

NScripterでボイス作成

 もしかしたら、荘大な無駄な努力かも知れん。

このエントリーの最終目標

NScripterで、ボイスファイルをつなぎ合わせてその場でセリフを作成すること。
もっとわかりやすく言うならば、NScripter

のようなことをやれないかな、と言う話。

応用

方法論

 ボイスを作成すると言うが、つまるところ「こんにちは」と言うセリフを言わせるために、「こ.wav」「ん.wav」「に.wav」「ち.wav」「わ.wav」を順繰りに再生する、そういう仕組みを作るだけの話である。
 ただし、これをプログラム的に表現すると「こ.wavを再生する。それが終わればん.wavを再生する。それが終われば(以下略」となるが、問題になるのは「終われば」と言う、演奏の終了判定である。
 automode命令によるオートスキップから、NScripterの内部ではこの終了判定が可能なことは推定できるが、マニュアルを見る限りそれを開発者やユーザーが使用する方法はないようだ。(将来的にこれが可能になれば、このエントリーで書かれたことは全てゴミクズと化す。きゃほー)
 次善の策として、演奏したいファイルの長さを事前に取得することが考えられる。演奏開始からカウントしてそれと同じ時間が経過した時、実際の演奏状況に関係なく終了とみなして次の処理に進む訳だ。
 では、ファイルの演奏時間を取得する方法について考えてみるが、ボイスにあわせて文字速度を変える。 - 永字八法では、事前も事前、作成段階で取得しておくようにしていたが、正直それはどうなんよと言わざるを得ない。データの持ち方としてはあまり美しくもない訳だし、スクリプターにとっても負担が大きい。
 ので、ファイルから直接演奏時間を取得することを考えた。
 その結果、後述のvoice_len.exeが完成し、wavに限定してだが、その演奏時間をミリ秒単位で取得することに成功した。
 後は、これをNScripterからDLLを介さずに他の言語で作られたアプリを使う。 - 永字八法を応用してNScripterから扱えるようにして、サンプルプログラムを作成した。

サンプル

voice_len.exeの中身。

 Windowsのwavの仕様は見つけたので、それに沿ってファイルヘッダーを解析するプログラムをPerlで組んでみた。wavのファイルヘッダーの中に、「データ部分の長さ」と「一秒あたりのバイト数」があるので、前者を後者で割り、1,000倍すると演奏時間(ミリ秒)になる。端数は切り捨てた。
 それをNScripterからDLLを介さずに他の言語で作られたアプリを使う。 - 永字八法に適合するように整えて、ppでアプリ化して完成である。

open ( IN, 'command.txt' );
my @file = <IN>;
close ( IN );
map { chomp $_ } @file;
map { chomp $_ } @file;
open ( OUT, '>result.txt' );
if ( 0 == scalar @file ) {
	print OUT '';
	exit(0);
}

my $line = '';
my @type = qw (
	RIFF
	SIZE-8
	WAVE
	fmt
	16
	format
	channel
	SAMPLING
	Bit/Sec
	Byte/S
	Bit/S
);
my %hack = (
	'RIFF'=>'A4',
	'SIZE-8'=>'L',
	'WAVE'=>'A4',
	'fmt'=>'A4',
	'16'=>'L',
	'format'=>'S',
	'channel'=>'S',
	'SAMPLING'=>'L',
	'Bit/Sec'=>'L',
	'Byte/S'=>'S',
	'Bit/S'=>'S',
);
	my %data = ();
	my $str = '';
	map { $str .= $hack{$_} } @type;
	my @result = ();
	my $length = 0;
	my @output = ();

foreach my $file ( @file ) {
	$length = 0;
	if ( open ( IN, $file ) ) {
		binmode ( IN );
		read ( IN, $line, 36 );
		%data = ();

		@result = unpack $str, $line;
		map { $data{$_} = shift @result } @type;

		unless ( $data{'16'} == 16 ) {
			read ( IN, $line, 2 );
			my $count = unpack 'S', $line;
			$count and read ( IN, $line, $count );
		}

		read ( IN, $line, 8 );
		my ( $data_chunk, $data_size ) = unpack 'A4L', $line;
		$data{'DATA'} = $data_chunk;
		$data{'DATA_SIZE'} = $data_size;
		$length = int ( 1000 * $data{'DATA_SIZE'} / $data{'Bit/Sec'} );
		close ( IN );
	}
	push @output, '"'.$file.'",'.$length;
}
print OUT join ( "\n", @output );

00.txtの中身

 NScripter側は、このように出迎えた。

*start

voice_len %1,"JNGL001.wav"
voice_len %2,"JNGL002.wav"
voice_len %3,"JNGL003.wav"
voice_len %4,"JNGL004.wav"

ファイル1:%1ミリ秒
dwave 0,"JNGL001.wav"
resettimer
waittimer %1

ファイル2:%2ミリ秒
dwave 0,"JNGL002.wav"
resettimer
waittimer %2

ファイル3:%3ミリ秒
dwave 0,"JNGL003.wav"
resettimer
waittimer %3

ファイル4:%4ミリ秒
dwave 0,"JNGL004.wav"
resettimer
waittimer %4

演奏終了\
end
*define

mov %0,100
numalias voice_len_file,%0:inc %0
numalias voice_len_len,%0:inc %0
defsub voice_len

game

*voice_len
; パラメータの取得
getparam i%voice_len_len,$voice_len_file
; 対象のファイルの有無のチェック
mov %%voice_len_len,-1 ; 初期化
fileexist %voice_len_file,$voice_len_file
if %voice_len_file=0 return ; 存在しないなら何もしない。
; 前回の実行結果を削除。
fileremove "result.txt"
; 書き込み
csvopen "command.txt","w"
csvwrite $voice_len_file
csvclose
; 実行
winexec "voice_len.exe",0
; 結果が出来上がるまで待つ。
wait 10
fileexist %voice_len_file,"result.txt"
if %voice_len_file=0 skip -2
csvopen "result.txt","r"
csvread $voice_len_file,%%voice_len_len
csvclose
return

; wavファイルは、「音楽工房」よりコピーしています。
; http://www.ddnj.com/product/sound/ok/index.html

今後の課題

 でもまあ、こういう外部アプリの使い方は究極ダサいよね。やっぱdllを作って、execdll命令から使ってみたいよね。
 と、言う訳で、誰かこの仕組みをCとかC++で書きなおして、リプレースしてくれないかなと他力本願で終わる。

おまえはマイノリティなんだよ!

目算を誤る - 永字八法の続き。
100円ショップに空気読まずメルフォ書いた結果が出た。

お問合せの収納袋の件ですが、
あいにくA5サイズは文庫本やコミックなどと異なり
シリーズでの収納用途が少なく、取扱いがございません。

つまり、エロ漫画は日陰の本である、と。……おのれっ! マイノリティなめんな!

NScripterでボイス作成

 もしかしたら、荘大な無駄な努力かも知れん。

このエントリーの最終目標

NScripterで、ボイスファイルをつなぎ合わせてその場でセリフを作成すること。
もっとわかりやすく言うならば、NScripter

のようなことをやれないかな、と言う話。

応用

方法論

 ボイスを作成すると言うが、つまるところ「こんにちは」と言うセリフを言わせるために、「こ.wav」「ん.wav」「に.wav」「ち.wav」「わ.wav」を順繰りに再生する、そういう仕組みを作るだけの話である。
 ただし、これをプログラム的に表現すると「こ.wavを再生する。それが終わればん.wavを再生する。それが終われば(以下略」となるが、問題になるのは「終われば」と言う、演奏の終了判定である。
 automode命令によるオートスキップから、NScripterの内部ではこの終了判定が可能なことは推定できるが、マニュアルを見る限りそれを開発者やユーザーが使用する方法はないようだ。(将来的にこれが可能になれば、このエントリーで書かれたことは全てゴミクズと化す。きゃほー)
 次善の策として、演奏したいファイルの長さを事前に取得することが考えられる。演奏開始からカウントしてそれと同じ時間が経過した時、実際の演奏状況に関係なく終了とみなして次の処理に進む訳だ。
 では、ファイルの演奏時間を取得する方法について考えてみるが、ボイスにあわせて文字速度を変える。 - 永字八法では、事前も事前、作成段階で取得しておくようにしていたが、正直それはどうなんよと言わざるを得ない。データの持ち方としてはあまり美しくもない訳だし、スクリプターにとっても負担が大きい。
 ので、ファイルから直接演奏時間を取得することを考えた。
 その結果、後述のvoice_len.exeが完成し、wavに限定してだが、その演奏時間をミリ秒単位で取得することに成功した。
 後は、これをNScripterからDLLを介さずに他の言語で作られたアプリを使う。 - 永字八法を応用してNScripterから扱えるようにして、サンプルプログラムを作成した。

サンプル

voice_len.exeの中身。

 Windowsのwavの仕様は見つけたので、それに沿ってファイルヘッダーを解析するプログラムをPerlで組んでみた。wavのファイルヘッダーの中に、「データ部分の長さ」と「一秒あたりのバイト数」があるので、前者を後者で割り、1,000倍すると演奏時間(ミリ秒)になる。端数は切り捨てた。
 それをNScripterからDLLを介さずに他の言語で作られたアプリを使う。 - 永字八法に適合するように整えて、ppでアプリ化して完成である。

open ( IN, 'command.txt' );
my @file = <IN>;
close ( IN );
map { chomp $_ } @file;
map { chomp $_ } @file;
open ( OUT, '>result.txt' );
if ( 0 == scalar @file ) {
	print OUT '';
	exit(0);
}

my $line = '';
my @type = qw (
	RIFF
	SIZE-8
	WAVE
	fmt
	16
	format
	channel
	SAMPLING
	Bit/Sec
	Byte/S
	Bit/S
);
my %hack = (
	'RIFF'=>'A4',
	'SIZE-8'=>'L',
	'WAVE'=>'A4',
	'fmt'=>'A4',
	'16'=>'L',
	'format'=>'S',
	'channel'=>'S',
	'SAMPLING'=>'L',
	'Bit/Sec'=>'L',
	'Byte/S'=>'S',
	'Bit/S'=>'S',
);
	my %data = ();
	my $str = '';
	map { $str .= $hack{$_} } @type;
	my @result = ();
	my $length = 0;
	my @output = ();

foreach my $file ( @file ) {
	$length = 0;
	if ( open ( IN, $file ) ) {
		binmode ( IN );
		read ( IN, $line, 36 );
		%data = ();

		@result = unpack $str, $line;
		map { $data{$_} = shift @result } @type;

		unless ( $data{'16'} == 16 ) {
			read ( IN, $line, 2 );
			my $count = unpack 'S', $line;
			$count and read ( IN, $line, $count );
		}

		read ( IN, $line, 8 );
		my ( $data_chunk, $data_size ) = unpack 'A4L', $line;
		$data{'DATA'} = $data_chunk;
		$data{'DATA_SIZE'} = $data_size;
		$length = int ( 1000 * $data{'DATA_SIZE'} / $data{'Bit/Sec'} );
		close ( IN );
	}
	push @output, '"'.$file.'",'.$length;
}
print OUT join ( "\n", @output );

00.txtの中身

 NScripter側は、このように出迎えた。

*start

voice_len %1,"JNGL001.wav"
voice_len %2,"JNGL002.wav"
voice_len %3,"JNGL003.wav"
voice_len %4,"JNGL004.wav"

ファイル1:%1ミリ秒
dwave 0,"JNGL001.wav"
resettimer
waittimer %1

ファイル2:%2ミリ秒
dwave 0,"JNGL002.wav"
resettimer
waittimer %2

ファイル3:%3ミリ秒
dwave 0,"JNGL003.wav"
resettimer
waittimer %3

ファイル4:%4ミリ秒
dwave 0,"JNGL004.wav"
resettimer
waittimer %4

演奏終了\
end
*define

mov %0,100
numalias voice_len_file,%0:inc %0
numalias voice_len_len,%0:inc %0
defsub voice_len

game

*voice_len
; パラメータの取得
getparam i%voice_len_len,$voice_len_file
; 対象のファイルの有無のチェック
mov %%voice_len_len,-1 ; 初期化
fileexist %voice_len_file,$voice_len_file
if %voice_len_file=0 return ; 存在しないなら何もしない。
; 前回の実行結果を削除。
fileremove "result.txt"
; 書き込み
csvopen "command.txt","w"
csvwrite $voice_len_file
csvclose
; 実行
winexec "voice_len.exe",0
; 結果が出来上がるまで待つ。
wait 10
fileexist %voice_len_file,"result.txt"
if %voice_len_file=0 skip -2
csvopen "result.txt","r"
csvread $voice_len_file,%%voice_len_len
csvclose
return

; wavファイルは、「音楽工房」よりコピーしています。
; http://www.ddnj.com/product/sound/ok/index.html

今後の課題

 でもまあ、こういう外部アプリの使い方は究極ダサいよね。やっぱdllを作って、execdll命令から使ってみたいよね。
 と、言う訳で、誰かこの仕組みをCとかC++で書きなおして、リプレースしてくれないかなと他力本願で終わる。