パスワードを忘れた? アカウント作成
14145599 journal
Perl

route127の日記: スコットランドPerl(^o^) 4

日記 by route127

テキストによる波形出力を行いffmpegで音声化するのが可能ならMML(もどき)の入力に対して波形出力するのもできるような気がして蛍の光を生成(演奏?)するPerlスクリプトを描いた。
下記コードをhotaru.plとでも保存して

とすればmp3ファイルが生成される。
一時ファイルを作らずに再生しようと

hotaru.pl | ffmpeg -f u8 -ar 44.1k -ac 1 -i - | ffplay -i -

というのも試したが上手くいかなかった。
use strict;
use warnings;

my $samprate = 44100;
my $tempo = 60; #四分音符換算
my $a = 440;
my $step = 2**(1/12);
my %freq_table = (
    'C' => $a*$step**-9,
    'D' => $a*$step**-7,
    'E' => $a*$step**-5,
    'F' => $a*$step**-4,
    'G' => $a*$step**-2,
    'A' => $a,
    'B' => $a*$step**2,
);
my %length_table = (
    '0' => 1/32,
    '1' => 1/16,
    '2' => 3/32,
    '3' => 1/8,
    '4' => 3/16,
    '5' => 1/4,
    '6' => 3/8,
    '7' => 1/2,
    '8' => 3/4,
    '9' => 1,
);
sub sound {
    my ($freq, $length) = @_;
    my $string = '';
    foreach my $t (0..$samprate*$length_table{$length}*240/$tempo){
        #$string .= chr(int(33+46.5*(1+sin(3.14*$freq_table{$freq}*$t/$samprate))));
        $string .= chr int (93*($freq_table{$freq}*$t/$samprate))%93+33;
    }
    $string;
}
my $hotaru = "
C5 F6 F3F5A5 G6F3G5 A5 F5 F5A5>C5 D8 D5
      C6<A3A5F5 G6F3G5 A5 F6 D3D5 C5 F8>D5
      C6<A3A5F5 G6F3G5>D5 C6<A3A4>C5 D8 D5
      C6<A3A5F5 G6F3G5 A5 F6 D3D5 C5 F8";
$hotaru =~ tr/ \n//d;
$hotaru =~ s/(?:>(?{grep{$_*=2}values %freq_table})|<(?{grep{$_*=0.5}values %freq_table})|([A-G])([0-9]))/sound($1, $2)/ge;
print $hotaru;

sedでアヴェマリアを弾くスレはタレこんでおいて何だがawkのときのこともあったので好意的な意見はあまり期待しないようにはしていた。
個人的にはこのマルチメディア時代にあっていにしえのテキスト処理ツールで波形出力というのがあたかもスペースシャトル代わりに棍棒で宇宙に行くのを見るようで興奮するところではある。

sedの出力眺めててチルダが出力されてるのは分かりつつもなんでこれが音声出力になるのか分かってなかったが、アスキーコードがそのまま振幅になってるのか。
ただそれをパイプでffmpegに送るところ

は膝を打った。
これを知ることができたのでタレこんだ甲斐はあったと思える。

音声処理というとoctaveとかの信号処理ツールでもってwavフォーマット読み込んで行うもの、というような先入観があった。
今ならさしずめPythonで、みたいな話になるのかもしれない。
一応PerlにもPDLみたいな信号処理用のモジュールはある。

今回はPDLを用いずPerlの文字列処理で440Hzの正弦波を1秒間鳴らすワンライナ

perl -e "$samprate=44100;$freq=440;foreach $t (0..$samprate){print chr int 33+46.5*(1+sin(3.14*$freq*$t/$samprate))}" | ffmpeg -f u8 -ar 44.1k -ac 1 -i - 440.mp3

から始めたが、なんかスピーカの鳴りが悪くてのこぎり波

perl -e "$samprate=44100;$freq=440;foreach $t (0..$samprate){print chr int (93*($freq*$t/$samprate))%93+33}"

で演奏することにした。
sound関数内弄ればその辺は改造できる。
あとオクターブ変えるのは音名と周波数のテーブル(%freq_table)を正規表現の中に埋め込んだPerlコードで制御してるのが工夫したところ。
長い曲だと誤差が累積して周波数が狂ってくるかもしれない。

Perlで書くと元のsedみたいな難読化的面白さは薄れてしまうけど、モジュールを使わずにperlとffmpegでこんなことができると思うと痛快だ。

  • > hotaru.pl | ffmpeg -f u8 -ar 44.1k -ac 1 -i - | ffplay -i -

    まず最初に一番簡単な回答。ffplayで再生するだけなら、

    > hotaru.pl | ffplay -f u8 -ar 44.1k -ac 1 -i -

    だけでOKです。ffplay のデータ入力側は ffmpeg と同じものなので、ffmpegが読めるデータは、ffplayでもそのまま再生できます。

    あとはおまけの解説。「ffmpeg…|ffplay -i -」が動かないのは、ffmpegにファイル出力指定がないから。出力ファイル名としての「-」(パイプ)指定と、出力形式の指定が必要です。(ffmpegで出力に普通のファイルを指定した場合は、ファイル名からフォーマットを自動判定してくれますが、パイプ出力の場合は判定できないので、明示的に指定する必要があります。)

    というわけで、ffmpeg経由の場合でも、

    > hotaru.pl | ffmpeg -f u8 -ar 44.1k -ac 1 -i - -f wav - | ffplay -i -

    と、最後に「-f wav -」を追加すれば再生できるようになります。

    今回の場合はパイプ処理は別に不要ですが、perl内部で入出力パイプ指定のffmpegを呼びだして「Perlスクリプトが直接mp3を出力する」みたいな応用もできるかと思います。

    #ちなみに、私は以前Excelで波形生成 [taka2.info]した時に、このffmpegのrawPCM入力にお世話になりました。
    #最終生成データはCのソースコード形式ですが、
    #試行錯誤の段階では、Excelで波形データをテキストで生成→Perlでバイナリ化→ffmpegでヘッダを付けてwavファイルに→聞いてみる、という流れで。

    ここに返信
    • > hotaru.pl | ffmpeg -f u8 -ar 44.1k -ac 1 -i - -f wav - | ffplay -i -

      -fオプションが2つあるじゃん…こんなん動くはずないでしょ……動いた…。

      なんかffmpegはオプションの順序変えると出力変わるとかいう話もあった気がする。

      >踏切
      ある程度編成長くしないと踏切の鳴動時間短そうだ。

      >Excelで波形生成
      Excelでレイトレ [youtube.com](対抗意識)

      • > -fオプションが2つあるじゃん…こんなん動くはずないでしょ……動いた…。

        入力指定「-i -」より前に指定した「-f u8 …」 は入力に関する設定で、その続きの
        出力指定「-」より前に指定した「-f wav」は出力の設定になります。

        入出力兼用のオプションはどこに入れるかで意味が変わるし、
        入力に関するオプションを入力指定よりあとに入れたり
        出力に関するオプションを出力指定よりあとに入れたら無視される、ということで順番大事。
        「オプションなんて順番関係ないでしょ」と考えてると、意味不明の動作に頭をかかえることになります…ffmpegは癖がありすぎ…

  • by Anonymous Coward on 2020年03月27日 11時50分 (#3786548)

    この日記エントリは楽しめました。感謝。

    ここに返信
typodupeerror

一つのことを行い、またそれをうまくやるプログラムを書け -- Malcolm Douglas McIlroy

読み込み中...