/* =============================================================================== SpectBmp.mc : 音声波形データを解析してその周波数スペクトル図を出力する =============================================================================== Copyright (C) 2010 Masahiko Watanabe Edition History: Ver.1.00 2010.12.18 -----------------------------------------------------------------------------*/ // デバッグプリント無効化(リリース時) //#set pc #comment //#set pv // デバッグプリント有効化(デバッグ時) #set pc print #set pv print #set p print //============================================================================= #pc "開始!"; #pv ::File.SetCurPath( ::Module.FilePath() ); #pc ::File.GetCurPath(); // スペクトル解析用DLLをリンク // ( この解析は、FFTではなく、LC並列共振フィルタによる ) #pv SpectDLL = DLL::Link( "Spectrum" ); if( SpectDLL == null ) { #p "Spectrum.dll が見つかりません!"; return; } #pv SetFilter = SpectDLL.GetEntry( "SetFilter" ); #pv SetWaveData = SpectDLL.GetEntry( "SetWaveData" ); #pv GetSpectrum = SpectDLL.GetEntry( "GetSpectrum" ); Q = 42; // 共振の鋭さ D = 256; // 共振周波数波形の分割数 #pc ##LC並列共振フィルタ特性: Q=${Q}, D=${D}##; SetFilter( Q, D ); // 波形データファイル( wav フォーマット )を読み込み if( argc <= 0 ) { #p "ウェーブファイルが指定されていません!"; goto END; } wave_file = argv[0]; call LOAD_WAVE_FILE; // 入力: wave_file : ウェーブファイル名 // 出力: Fs : ウェーブファイルのサンプリング周波数[Hz] // Ts : ウェーブファイルのサンプリング周期[sec.] // Ns : ウェーブファイルのサンプリングデータ数 // TT : ウェーブファイルの再生時間[sec.] // Wave'SHORT( Ns ) : 波形データ SetWaveData( Wave(0)'addr, Ns'int, Fs'int ); Fg = 100; // 周波数スペクトルゲインのサンプリング周波数[Hz] Tg = 1.0 / Fg; // 周波数スペクトルゲインのサンプリング周期[sec.] Ng = ( Fg * TT )'int; // 周波数スペクトルゲインのサンプリングデータ数 #pc ##Fg=${Fg}[Hz], Tg=${Tg*1000}[ms], Ng=${Ng}##; // 可聴範囲の周波数を所定の高低差で分割した周波数の一覧表( 分割数:240 ) Fclist'DOUBLE( 240 ) = { 20.0152, 20.6017, 21.2054, 21.8268, 22.4663, 23.1247, 23.8023, 24.4997, 25.2176, 25.9565, // 10 26.7171, 27.5000, 28.3058, 29.1352, 29.9890, 30.8677, 31.7722, 32.7032, 33.6615, 34.6478, // 20 35.6631, 36.7081, 37.7837, 38.8909, 40.0305, 41.2034, 42.4108, 43.6535, 44.9327, 46.2493, // 30 47.6045, 48.9994, 50.4352, 51.9131, 53.4343, 55.0000, 56.6116, 58.2705, 59.9779, 61.7354, // 40 63.5444, 65.4064, 67.3229, 69.2957, 71.3262, 73.4162, 75.5675, 77.7817, 80.0609, 82.4069, // 50 84.8216, 87.3071, 89.8653, 92.4986, 95.2090, 97.9989, 100.87, 103.826, 106.869, 110.000, // 60 113.223, 116.541, 119.956, 123.471, 127.089, 130.813, 134.646, 138.591, 142.652, 146.832, // 70 151.135, 155.563, 160.122, 164.814, 169.643, 174.614, 179.731, 184.997, 190.418, 195.998, // 80 201.741, 207.652, 213.737, 220.000, 226.446, 233.082, 239.912, 246.942, 254.178, 261.626, // 90 269.292, 277.183, 285.305, 293.665, 302.270, 311.127, 320.244, 329.628, 339.286, 349.228, // 100 359.461, 369.994, 380.836, 391.995, 403.482, 415.305, 427.474, 440.000, 452.893, 466.164, // 110 479.823, 493.883, 508.355, 523.251, 538.584, 554.365, 570.609, 587.330, 604.540, 622.254, // 120 640.487, 659.255, 678.573, 698.456, 718.923, 739.989, 761.672, 783.991, 806.964, 830.609, // 130 854.948, 880.000, 905.786, 932.328, 959.647, 987.767, 1016.71, 1046.50, 1077.17, 1108.73, // 140 1141.22, 1174.66, 1209.08, 1244.51, 1280.97, 1318.51, 1357.15, 1396.91, 1437.85, 1479.98, // 150 1523.34, 1567.98, 1613.93, 1661.22, 1709.90, 1760.00, 1811.57, 1864.66, 1919.29, 1975.53, // 160 2033.42, 2093.0, 2154.33, 2217.46, 2282.44, 2349.32, 2418.16, 2489.02, 2561.95, 2637.02, // 170 2714.29, 2793.83, 2875.69, 2959.96, 3046.69, 3135.96, 3227.85, 3322.44, 3419.79, 3520.00, // 180 3623.14, 3729.31, 3838.59, 3951.07, 4066.84, 4186.01, 4308.67, 4434.92, 4564.88, 4698.64, // 190 4836.32, 4978.03, 5123.90, 5274.04, 5428.58, 5587.65, 5751.38, 5919.91, 6093.38, 6271.93, // 200 6455.71, 6644.88, 6839.58, 7040.00, 7246.29, 7458.62, 7677.17, 7902.13, 8133.68, 8372.02, // 210 8617.34, 8869.84, 9129.75, 9397.27, 9672.63, 9956.06, 10247.8, 10548.1, 10857.2, 11175.3, // 220 11502.8, 11839.8, 12186.8, 12543.9, 12911.4, 13289.8, 13679.2, 14080.0, 14492.6, 14917.2, // 230 15354.3, 15804.3, 16267.4, 16744.0, 17234.7, 17739.7, 18259.5, 18794.5, 19345.3, 19912.1 }; Nf = Fclist'count; #pc ##Nf=${Nf}: スペクトル分割数##; // 周波数スペクトルを取得 Spect'ULONG( Nf, Ng ); // 周波数スペクトル値の配列 for( i = 0 ; i < Nf ; i++ ) { Fc = Fclist( i ); #pc ##◆${i}: Fc=${Fc}##; GetSpectrum( Spect( i, 0 )'addr, Ng, Fg, Fc ); } // 周波数スペクトルに対応する画素データを設定 SpectHeight = Nf; SpectWidth = Ng; SpectScale = 0; // スペクトル値の尺度( =0:対数, =1:線形 ) // 周波数スペクトルの画素データ配列 Sv'UBYTE( SpectHeight, ( SpectWidth + 3 ) & 0xFFC ); max = 1; for( i = 0 ; i < Nf ; i++ ) for( j = 0 ; j < Ng ; j++ ) if( max < Spect(i,j) ) max = Spect(i,j); #pc ##max=${max}##; Log := ::Math.log; log_max = Log( max + 1 ); k = Nf - 1; if( SpectScale == 0 ) // スペクトル値の尺度は対数? { // 範囲自動調整(対数) for( i = 0 ; i < Nf ; i++ ) for( j = 0 ; j < Ng ; j++ ) Sv(i,j) = 255 * Log( Spect(i,j) + 1 ) / log_max ; //Sv( k - i, j ) = 255 * Log( Spect(i,j) + 1 ) / log_max ; } else // スペクトル値の尺度は線形 { // 範囲自動調整(リニア) for( i = 0 ; i < Nf ; i++ ) for( j = 0 ; j < Ng ; j++ ) Sv(i,j) = 255 * Spect(i,j) / max; //Sv( k - i, j ) = 255 * Spect(i,j) / max; } delete Spect; // 周波数スペクトルを表すBMPファイルを作成 SpectFreq = Fg; // スペクトル値のサンプリング周波数[Hz] SpectColor = 0; // スペクトル値の表示色( =0:白黒, =1:虹色 ) if( ::File.GetNamePart( wave_file, , bmp_file'new! ) ) bmp_file += ".bmp"; else bmp_file = "xxx.bmp"; call SAVE_BMP_FILE; // 入力: Sv : 周波数スペクトルの画素データ配列 // SpectHeight: スペクトル画像の縦の画素数 // SpectWidth : スペクトル画像の横の画素数 // SpectScale : スペクトル値の尺度( =0:対数, =1:線形 ) // SpectFreq : スペクトル値のサンプリング周波数[Hz] // SpectColor : スペクトル値の表示色( =0:白黒, =1:虹色 ) // bmp_file : 周波数スペクトルのビットマップファイル名 // 出力: 周波数スペクトルのビットマップファイル #pc "終了!"; goto END; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - END: delete SpectDLL; delete fi, fo; return; ERROR: #pc "失敗!"; goto END; //============================================================================= // ウェーブファイルの読み込み // // 入力: wave_file : ウェーブファイル名 // 出力: Fs : ウェーブファイルのサンプリング周波数[Hz] // Ts : ウェーブファイルのサンプリング周期[sec.] // Ns : ウェーブファイルのサンプリングデータ数 // TT : ウェーブファイルの再生時間[sec.] // Wave'SHORT( Ns ) : 波形データ LOAD_WAVE_FILE: fi = ::File.Open( wave_file, "in" ); if( fi == null ) { #p ##${wave_file} が、オープンできません!##; warp ERROR; } RH ::= // RIFF Header { .RiffId 'C(4); // Riff chunk id ( = 'RIFF' ) .RiffSize 'ULONG; // Riff chunk size ( = - 8 ) .WaveId 'C(4); // = 'WAVE' } if( fi.Read( RH ) != RH'size ) goto LWF_READ_ERROR; #pc RH.RiffId, RH.RiffSize, RH.WaveId; if( RH.RiffId != "RIFF" || RH.WaveId != "WAVE" ) goto LWF_DATA_ERROR; CH ::= // Chunk Header { .ChunkId 'C(4); // chunk id ( = 'fmt ', 'data', etc. ) .ChunkSize 'ULONG; // chunck size ( except this header size ) } for( ;; ) { if( fi.Read( CH ) != CH'size ) goto LWF_READ_ERROR; #pc CH.ChunkId, CH.ChunkSize; si = fi.Seek() + CH.ChunkSize; switch( CH.ChunkId ) { case "fmt ": FC ::= // フォーマットチャンク { .FormatType 'USHORT; // format type ( =1: PCM ) .Channels 'USHORT; // number of channels ( =1: mono, =2:stereo ) .SamplesPerSec 'ULONG; // sample rate [Hz] ( = 11025, 22050, or 44100 ) .AvgBytesPerSec 'ULONG; // = .Channels * .SamplesPerSec * 16 / 8 ; .BlockAlign 'USHORT; // = .Channels * .BitsPerSample / 8 .BitsPerSample 'USHORT; // # of bits of sampling data ( = 16 or 8 ) // 拡張データ ... } if( fi.Read( FC ) != FC'size ) goto LWF_READ_ERROR; #pc FC.FormatType, FC.Channels, FC.SamplesPerSec, FC.AvgBytesPerSec, FC.BlockAlign, FC.BitsPerSample; if( FC.FormatType != 1 || FC.Channels != 1 ) goto LWF_DATA_ERROR; break; case "data": // データチャンク if( ! FC'exist? ) goto LWF_DATA_ERROR; Ns = CH.ChunkSize / FC.BlockAlign; Fs = FC.SamplesPerSec'float; Ts = 1.0 / Fs; TT = Ns / Fs; #pc ##${wave_file}: Fs=${Fs/1000}[KHz], Ns=${Ns}, TT=${TT*1000}[ms]##; quit; default: #pc ##チャンク '${CH.ChunkId}' は、無視します!##; break; } if( fi.Seek( si, "top" ) != si ) goto LWF_DATA_ERROR; } Wave'SHORT( Ns ); // 波形データの配列 switch( FC.BitsPerSample ) { case 8: bv'UBYTE( Ns ); if( fi.Read( bv ) != bv'size ) goto LWF_READ_ERROR; for( i = 0 ; i < Ns ; i++ ) Wave(i) = bv(i) - 128; delete bv; break; case 16: if( fi.Read( Wave ) != Wave'size ) goto LWF_READ_ERROR; break; default: goto LWF_DATA_ERROR; } delete fi; back; LWF_READ_ERROR: #p ##${wave_file}: 読み込みエラー!##; warp ERROR; LWF_DATA_ERROR: #p ##${wave_file}: データが想定外です!##; warp ERROR; //============================================================================= // 周波数スペクトルをビットマップファイルに保存 // // 入力: Sv : 周波数スペクトルの画素データ配列 // SpectHeight: スペクトル画像の縦の画素数 // SpectWidth : スペクトル画像の横の画素数 // SpectScale : スペクトル値の尺度( =0:対数, =1:線形 ) // SpectFreq : スペクトル値のサンプリング周波数[Hz] // SpectColor : スペクトル値の表示色( =0:白黒, =1:虹色 ) // bmp_file : 周波数スペクトルのビットマップファイル名 // 出力: 周波数スペクトルのビットマップファイル SAVE_BMP_FILE: // 拡張情報の設定 if( ! SpectScale'exist? ) SpectScale = 0; // スペクトル値の尺度は対数 if( ! SpectFreq'exist? ) SpectFreq = 100; // スペクトル値のサンプリング周波数 100 Hz bxi ::= { .SpectId 'C(5) = "Spect"; .SpectScale 'UBYTE = SpectScale; // スペクトル値の尺度( =0:対数, =1:線形 ) .SpectFreq 'ULONG = SpectFreq; // スペクトル値のサンプリング周波数[Hz] } // BMPファイルヘッダー bfh ::= { .bfType 'C(2) = "BM"; .bfSize 'ULONG = 0; // ファイルサイズ・・・後に設定 .bfReserved1 'USHORT = 0; .bfReserved2 'USHORT = 0; #pv .bfOffBits 'ULONG = (( 14 + 40 + 256*4 + bxi'size ) + 15 ) & 0xFFFF`FFF0; } #pv bfh.bfSize = bfh.bfOffBits + Sv'size; bih ::= { .biSize 'ULONG = 40; .biWidth 'LONG = SpectWidth; .biHeight 'LONG = SpectHeight; .biPlanes 'USHORT = 1; .biBitCount 'USHORT = 8; .biCompression 'ULONG = 0; .biSizeImage 'ULONG = 0; .biXPelsPerMeter'LONG = 0; .biYPelsPerMeter'LONG = 0; .biClrUsed 'ULONG = 0; .biClrImportant 'ULONG = 0; } // パレットの設定 pal'ULONG( 256 ); if( ! SpectColor'exist? || SpectColor == 0 ) // スペクトル値の表示色は白黒? { for( i = 0 ; i < 256 ; i++ ) pal(i) = ( i << 16 ) + ( i << 8 ) + i; } else // スペクトル値の表示色は虹色 { for( i = 0 ; i < 256 ; i++ ) { u = ( i & 0x3F ) << 2; v = u ^ 0xFC; switch( i & 0xC0 ) { case 0x00: r = 0; g = u; b = 63*4; break; case 0x40: r = 0; g = 63*4; b = v; break; case 0x80: r = u; g = 63*4; b = 0; break; case 0xC0: r = 63*4; g = v; b = 0; break; } pal(i) = ( r << 16 ) + ( g << 8 ) + b; } } // ファイルへの書き込み #pc bmp_file; #pv fo = File.Open( bmp_file, "out" ); if( fo == null ) { #p ##${bmp_file} が、オープンできません!##; warp ERROR; } #pv fo.Write( bfh ); #pv fo.Write( bih ); #pv fo.Write( pal ); #pv fo.Write( bxi ); #pv fo.Seek( bfh.bfOffBits ); #pv fo.Write( Sv ); #pv fo.Close(); back; //=============================================================================