LECTURE 12 複数の音を足す単純ミキサ

○ はじめに

色々な楽器(ボーカルも含めて)を使って 1 つの曲を作ろうとする場合、普通はまず単一楽器のみを録ったトラックを作り、それらを合成して曲に仕上げます(ミックスダウン)。その際、トラックごとに音量や定位(音源の位置)を調整したり、エフェクトをかけたりすることで気持ちよく聞ける音を作ることが出来ます。

ここではそんなにちゃんとした物ではなく、「2 つの音源を単純に合成して 1 つの音を作り出す」低性能ミキサを書いてみたいと思います。ちゃんとした物を作るには、著者の音に関する知識が足りなさ過ぎますので^^;

何故これを書こうと思ったかと言いますと、以前 Linux® で動くゲームを作ろうとした時に効果音を出そうとしたのですが、昔のサウンドドライバは複数の音を同時にデバイスファイルに書き込んでも同時に鳴らしてくれなかったからです。何時だったかは忘れましたが、そのうち同時に書き込めばドライバの方で合成してくれる様になったという記憶がかすかにあるのですが、タイミングの制御が確実ではないことやドライバの動作を調べるのが面倒だったので、結局自前で合成するライブラリを作りました。

その時の経験を残すという意味で、本ページを書いてみました。

○ サンプルプログラム

指定した 2 つの WAVE ファイルについて、片方のみを再生したり 2 つを足し合わせて同時再生するサンプルプログラム mix.c です。はじめに のページから、全てのサンプルプログラムを固めた tar ボールをダウンロードすることもできます。

pthread を使っていますので

gcc -o mix mix.c -lpthread

の様に -lpthread オプションを付けてコンパイルします。

引数に 2 つの WAVE ファイルの絶対パスを指定して実行すると、以下の様なメニューが表示されます。番号を入力して Enter を押すと、指定した内容に従って WAVE ファイルを再生します。

Select menu ...
  1. Play first  specified wave file only.
  2. Play second specified wave file only.
  3. Play both wave files simultaneously.
  4. Stop playing.
 99. Exit.

Command ?  

指定できる WAVE ファイルは

  • PCM フォーマット
  • 量子化ビット数 16 ビット
  • 1 チャンネル

です。また、2 つの WAVE ファイルは同じサンプリングレートでなければいけません。

--- mix.c ---

    1	#include <fcntl.h> 
    2	#include <limits.h>
    3	#include <stdio.h>
    4	#include <stdlib.h>
    5	#include <string.h>
    6	#include <linux/soundcard.h>
    7	#include <pthread.h>
    8	#include <sys/ioctl.h>
    9	#include <unistd.h>
   10	
   11	
   12	#define COMMAND_STOP        0
   13	#define COMMAND_PLAY_FIRST  1
   14	#define COMMAND_PLAY_SECOND 2
   15	#define COMMAND_PLAY_BOTH   3
   16	#define COMMAND_EXIT        4
   17	
   18	#define BUFSIZE 4096
   19	
   20	
   21	/* WAVE ファイルの情報を格納する構造体 */
   22	typedef struct {
   23		FILE* fp;        /* ファイル構造体 */
   24		short is_pcm;    /* PCM フォーマットの場合は 1、それ以外は 0 */
   25		short channel;   /* モノラルの場合は 1、ステレオの場合は 2 */
   26		int   rate;      /* サンプリング周波数 */
   27		short bits;      /* 量子化ビット数 */
   28		long  offset;    /* ファイル先頭から PCM データまでのオフセット */
   29		int   len;       /* PCM データ部の長さ */
   30		int   current;   /* 現在の再生位置 */
   31	}
   32	WAVE;
   33	
   34	
   35	/* 再生に必要なデータをまとめた構造体 */
   36	typedef struct {
   37		pthread_t thread_id;
   38		int command;
   39		int  dsp;
   40		WAVE wave[2];
   41		short buffer[ BUFSIZE / sizeof( short ) ];
   42	}
   43	Context;
   44	
   45	static Context context;
   46	
   47	
   48	static int   read_file( char** fnames, WAVE* wave );
   49	static int   setup_dsp( int* fd, WAVE* wave );
   50	static void  show_menu( void );
   51	static void* play_thread( void* param );
   52	static void  fill_buffer( short* dst, WAVE* wave );
   53	static void  mix( short* dst, WAVE* wave );
   54	static void  shutdown();
   55	
   56	
   57	int 
   58	main( int argc, char** argv )
   59	{
   60		pthread_attr_t attr;
   61		int ret;
   62	
   63		if ( argc != 3 ) {
   64			fprintf( stderr, "usage : ./wave filename1 filename2\n" );
   65			return 1;
   66		}
   67	
   68		if ( read_file( &( argv[1] ), &( context.wave[0] ) ) != 0 ) {
   69			fprintf( stderr, "Failed to read wave files.\n" );
   70			return 1;
   71		}
   72	
   73		if ( setup_dsp( &( context.dsp ), &( context.wave[0] ) ) != 0 ) {
   74			fprintf( stderr, "Failed to setup /dev/dsp.\n" );
   75			return 1;
   76		}
   77	
   78		pthread_attr_init( &attr );
   79		pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE );
   80	
   81		ret = pthread_create( &( context.thread_id ), &attr, 
   82				      play_thread, ( void* )( &context ) );
   83	
   84		pthread_attr_destroy( &attr );
   85	
   86		if ( ret ) {
   87			fprintf( stderr, "Failed to create thread.\n" );
   88			shutdown();
   89			return 1;
   90		}
   91	
   92		show_menu();
   93		pthread_join( context.thread_id, NULL );
   94		shutdown();
   95	
   96		return 0;
   97	}
   98	
   99	
  100	static int 
  101	read_file( char** fnames, WAVE* wave )
  102	{
  103		char buf[32];
  104		int  len;
  105		int  i;
  106	
  107		for ( i = 0; i < 2; i ++ ) {
  108			if ( ( wave[i].fp = fopen( fnames[i], "r" ) ) == NULL ) {
  109				fprintf( stderr, "Failed to open %s\n", fnames[i] );
  110				goto err;
  111			}
  112	
  113			/* 
  114			 * 先頭 4 バイトが "RIFF" であることを確認 
  115			 * 更に 4 バイトスキップしておく
  116			 */
  117			fread( buf, 8, 1, wave[i].fp );
  118	
  119			if ( strncmp( buf, "RIFF", 4 ) != 0 ) {
  120				fprintf( stderr, "Specified file is not RIFF file.\n" );
  121				goto err;
  122			}
  123	
  124	
  125			/* 次の 4 バイトが "WAVE" であることを確認 */
  126			fread( buf, 4, 1, wave[i].fp );
  127	
  128			if ( strncmp( buf, "WAVE", 4 ) != 0 ) {
  129				fprintf( stderr, "Specified file is not WAVE file.\n" );
  130				goto err;
  131			}
  132	
  133			/* fmt チャンクを探す */
  134			while ( 1 ) {
  135				fread( buf, 8, 1, wave[i].fp );
  136				len = *( int* )( &buf[4] );
  137	
  138				if ( strncmp( buf, "fmt ", 4 ) != 0 ) {
  139					if ( fseek( wave[i].fp, len, SEEK_CUR ) == -1 ) {
  140						fprintf( stderr, "Failed to find fmt.\n" );
  141						goto err;
  142					}
  143				}
  144				else {
  145					break;
  146				}
  147			}
  148	
  149			fread( buf, len, 1, wave[i].fp );
  150	
  151			wave[i].is_pcm  = *( ( short* )( &buf[0]  ) );
  152			wave[i].channel = *( ( short* )( &buf[2]  ) );
  153			wave[i].rate    = *( ( int*   )( &buf[4]  ) );
  154			wave[i].bits    = *( ( short* )( &buf[14] ) );
  155	
  156			/*
  157			 * PCM / 16 bits / Monoral 以外の WAVE データについては
  158			 * 本プログラムでは対象外
  159			 */
  160			if ( wave[i].is_pcm != 1 ) {
  161				fprintf( stderr, "WAVE file must be PCM format.\n" );
  162				goto err;
  163			}
  164	
  165			if ( wave[i].bits != 16 ) {
  166				fprintf( stderr, "WAVE file must be 16 bits .\n" );
  167				goto err;
  168			}
  169	
  170			if ( wave[i].channel != 1 ) {
  171				fprintf( stderr, "WAVE file must be monoral.\n" );
  172				goto err;
  173			}
  174	
  175			/* data チャンクを探す */
  176			while ( 1 ) {
  177				fread( buf, 8, 1, wave[i].fp );
  178				len = *( int* )( &buf[4] );
  179	
  180				if ( strncmp( buf, "data", 4 ) != 0 ) {
  181					if ( fseek( wave[i].fp, len, SEEK_CUR ) == -1 ) {
  182						fprintf( stderr, "Failed to find data.\n" );
  183						goto err;
  184					}
  185				}
  186				else {
  187					break;
  188				}
  189			}
  190	
  191			wave[i].len = len;
  192	
  193			if ( ( wave[i].offset = ftell( wave[i].fp ) ) == -1 ) {
  194				fprintf( stderr, "Failed to find offset of PCM data.\n" );
  195				goto err;
  196			}
  197	
  198			printf( "\n" );
  199			printf( "WAVE file format of %s:\n", fnames[i] );
  200			printf( " rate    = %d\n",  wave[i].rate );
  201			printf( " channel = %d\n",  wave[i].channel );
  202			printf( " bits    = %d\n",  wave[i].bits );
  203			printf( " offsets = %ld\n", wave[i].offset );
  204			printf( " length  = %d\n",  wave[i].len );
  205			printf( "\n" );
  206		}
  207		
  208		if ( wave[0].rate != wave[1].rate ) {
  209			fprintf( stderr, "Specified 2 WAVE files "
  210				 "must have the same sampling rate.\n" );
  211			goto err;
  212		}
  213	
  214		return 0;
  215	
  216	err:
  217		if ( wave[0].fp != NULL ) {
  218			fclose( wave[0].fp );
  219		}
  220	
  221		if ( wave[1].fp != NULL ) {
  222			fclose( wave[1].fp );
  223		}
  224	
  225		return 1;
  226	}
  227	
  228	
  229	static int 
  230	setup_dsp( int* dsp, WAVE* wave )
  231	{
  232		int fragment = 0x0004000c; /* size = 4096 bytes, num of fragments = 4. */
  233		int format = AFMT_S16_LE;
  234		int rate;
  235		int channel;
  236	
  237		rate    = ( int )wave[0].rate;
  238		channel = ( int )wave[0].channel;
  239	
  240		if ( ( *dsp = open( "/dev/dsp", O_WRONLY ) ) == -1 ) {
  241			perror( "open()" );
  242			return 1;
  243		}
  244	
  245		if ( ioctl( *dsp, SNDCTL_DSP_SETFRAGMENT, &fragment ) == -1 ) {
  246			perror( "ioctl( SNDCTL_DSP_SETFRAGMENT )" );
  247			close( *dsp );
  248			return 1;
  249		}
  250	
  251		if ( ioctl( *dsp, SNDCTL_DSP_SETFMT, &format ) == -1 ) {
  252			perror( "ioctl( SNDCTL_DSP_SETFMT )" );
  253			close( *dsp );
  254			return 1;
  255		}
  256	
  257		if ( ioctl( *dsp, SNDCTL_DSP_SPEED, &rate ) == -1 ) {
  258			perror( "ioctl( SNDCTL_DSP_SPEED )" );
  259			close( *dsp );
  260			return 1;
  261		}
  262	
  263		if ( ioctl( *dsp, SNDCTL_DSP_CHANNELS, &channel ) == -1 ) {
  264			perror( "ioctl( SNDCTL_DSP_CHANNELS )" );
  265			close( *dsp );
  266			return 1;
  267		}
  268	
  269		return 0;
  270	}
  271	
  272	
  273	static void 
  274	show_menu( void )
  275	{
  276		char line[4];
  277		int  command;
  278	
  279		while ( 1 ) {
  280			printf( "Select menu ...\n" );
  281			printf( "  1. Play first  specified wave file only.\n" );
  282			printf( "  2. Play second specified wave file only.\n" );
  283			printf( "  3. Play both wave files simultaneously.\n" );
  284			printf( "  4. Stop playing.\n" );
  285			printf( " 99. Exit.\n" );
  286			printf( "\n" );
  287			printf( "Command ? " );
  288	
  289			fgets( line, sizeof( line ), stdin );
  290			sscanf( line, "%d\n", &command );
  291	
  292			switch ( command ) {
  293				case 1:
  294					context.command = COMMAND_PLAY_FIRST;
  295					break;
  296				case 2:
  297					context.command = COMMAND_PLAY_SECOND;
  298					break;
  299				case 3:
  300					context.command = COMMAND_PLAY_BOTH;
  301					break;
  302				case 4:
  303					context.command = COMMAND_STOP;
  304					break;
  305				case 99:
  306					context.command = COMMAND_EXIT;
  307					return;
  308			}
  309		}
  310	}
  311	
  312	
  313	static void*
  314	play_thread( void* param )
  315	{
  316		Context* context = ( Context* )param;
  317	
  318		while ( context->command != COMMAND_EXIT ) {
  319			memset( ( void* )( context->buffer ), 0, BUFSIZE );
  320	
  321			switch ( context->command ) {
  322				case COMMAND_PLAY_FIRST:
  323					fill_buffer( ( void* )( context->buffer ), 
  324						     &( context->wave[0] ) );
  325					break;
  326	
  327				case COMMAND_PLAY_SECOND:
  328					fill_buffer( ( void* )( context->buffer ), 
  329						     &( context->wave[1] ) );
  330					break;
  331	
  332				case COMMAND_PLAY_BOTH:
  333					mix( ( void* )( context->buffer ), 
  334					     &( context->wave[0] ) );
  335					break;
  336	
  337				case COMMAND_STOP:
  338				default:
  339					break;
  340			}
  341	
  342			write( context->dsp, ( void* )( context->buffer ), BUFSIZE );
  343		}
  344	
  345		pthread_exit( NULL );
  346	}
  347	
  348	
  349	static void
  350	fill_buffer( short* dst, WAVE* wave )
  351	{
  352		int copied = 0;
  353	
  354		while ( 1 ) {
  355			int src_left = wave->len - wave->current;
  356			int dst_left = BUFSIZE - copied;
  357			int copy_len = src_left > dst_left ? dst_left : src_left;
  358	
  359			if ( src_left == 0 ) {
  360				fseek( wave->fp, wave->offset, SEEK_SET );
  361				wave->current = 0;
  362				continue;
  363			}
  364	
  365			if ( dst_left == 0 ) {
  366				break;
  367			}
  368	
  369			fread( ( void* )( dst + copied / sizeof( short ) ), 
  370			       copy_len, 1, wave->fp );
  371			wave->current += copy_len;
  372			copied += copy_len;
  373		}
  374	}
  375	
  376	
  377	#define MIN( a, b ) ( ( (a) < (b) ) ? (a) : (b) )
  378	#define MAX( a, b ) ( ( (a) > (b) ) ? (a) : (b) )
  379	
  380	
  381	static void
  382	mix( short* dst, WAVE* wave )
  383	{
  384		short src[ BUFSIZE ];
  385		int copied;
  386		int dst_index;
  387		int i;
  388		int j;
  389	
  390		for ( i = 0; i < 2; i ++ ) {
  391			memset( src, 0, BUFSIZE );
  392			copied = 0;
  393	
  394			while ( 1 ) {
  395				int src_left = wave[i].len - wave[i].current;
  396				int dst_left = BUFSIZE - copied;
  397				int copy_len = src_left > dst_left ? dst_left : src_left;
  398	
  399				if ( src_left == 0 ) {
  400					fseek( wave[i].fp, wave[i].offset, SEEK_SET );
  401					wave[i].current = 0;
  402					continue;
  403				}
  404	
  405				if ( dst_left == 0 ) {
  406					break;
  407				}
  408	
  409				fread( ( void* )src, copy_len, 1, wave[i].fp );
  410				
  411				for ( j = 0; j < copy_len / sizeof( short ); j ++ ) {
  412					dst_index = copied / sizeof( short ) + j;
  413	
  414					if ( src[j] < 0 ) {
  415						dst[ dst_index ] = ( short )MAX( 
  416							(int)dst[ dst_index ] + (int)src[j], 
  417							(int)SHRT_MIN );
  418					}
  419					else {
  420						dst[ dst_index ] = ( short )MIN( 
  421							(int)dst[ dst_index ] + (int)src[j], 
  422							(int)SHRT_MAX );
  423					}
  424				}
  425	
  426				wave[i].current += copy_len;
  427				copied += copy_len;
  428			}
  429		}
  430	}
  431	
  432	
  433	static void
  434	shutdown( void )
  435	{
  436		fclose( context.wave[0].fp );
  437		fclose( context.wave[1].fp );
  438		close( context.dsp );
  439	}
  440	
  441	
  442	/* End of mix.c */

○ サンプルプログラムの説明

では、サンプルプログラムについて説明していきます。

  • 7 行目

    WAVE ファイル再生を別スレッドで行うために pthread を使います。そのため pthread.h をここでインクルードしています。

  • 21 〜 32 行目

    WAVE ファイル情報を格納するための構造体を定義しています。LECTURE 10 WAVE ファイルを再生する のサンプルプログラムで定義している構造体に、現在の再生位置を表す current メンバを追加しただけです。音を加算する際の始点として使用します。

  • 35 〜 45 行目

    WAVE ファイル再生スレッドで使用する変数を、一つの構造体にまとめて定義しています。そしてファイルスコープを持つ context 変数を宣言しています。

  • 57 〜 97 行目 ( main() )

    mix.c の main 関数です。各処理について以下で説明していきます。

    • 68 〜 76 行目

      68 〜 71 行目で、指定された 2 つの WAVE ファイルを読み込む自作関数 read_file() を実行しています。その後 73 〜 76 行目で、自作関数 setup_dsp() を実行し、/dev/dsp の初期化を行っています。いずれも詳細は後程説明します。

    • 78 〜 90 行目

      ここでは、WAVE ファイル再生スレッドの生成/実行開始を行っています。81 行目の pthread_create() で、WAVE ファイル再生スレッドを生成します。生成されたスレッドは自動的に実行開始されます。

      int pthread_create( pthread_t* thread, pthread_attr_t* attr, 
          void* ( *start_routine )( void* ), void* arg );
      
      thread        : 生成されたスレッド ID が格納される
      attr          : スレッドに設定する属性を指定
      start_routine : スレッドの実行関数
      arg           : start_routine 実行時に引数に渡されるポインタ
      
      

      スレッド生成に成功した場合は 0 を、失敗した場合は EAGAIN を返します。本サンプルでは play_thread() という自作関数をスレッドの実行関数に指定し、前述の context 変数を引数に渡す様に指定しています。

      pthread_create() の attr に NULL を指定した場合、生成されたスレッドの終了を別スレッドから pthread_join() にて待つことが出来ます。ここでは NULL を指定せず、同じ内容を pthread_attr_setdetachstate() を使って明示的に指定しています。

      int pthread_attr_setdetachstate( pthread_attr_t* attr, int detachstate );
      
      attr        : 属性を格納する pthread_attr_t 型変数
      detachstate : PTHREAD_CREATE_JOINABLE (pthread_join() で待機可能) か
                    PTHREAD_CREATE_DETACHED (pthread_join() で待機不可)
      
      

      成功した場合は 0 を、失敗した場合は EINVAL を返します。また、pthread_attr_t 型変数は pthread_attr_init() / pthread_attr_destroy() を使って初期化/解放を行う必要があります。

      int pthread_attr_init( pthread_attr_t* attr );
      int pthread_attr_destroy( pthread_attr_t* attr );
      
      
    • 92 〜 94 行目

      main() を実行しているスレッドでは、自作関数の show_menu() によってメニュー表示/ユーザ入力の取得を行います。show_menu() が終了した(ユーザが終了を指示した)場合は、pthread_join() にて WAVE 再生スレッドの終了を待機した上で、自作関数の shutdown() を実行して /dev/dsp 等のリソースを解放しています。

  • 100 〜 226 行目 ( read_file() )

    指定された 2 つの WAVE ファイルの読み込みを行います。内容はほとんど LECTURE 10 WAVE ファイルを再生する のサンプルプログラム wave.c の wave_read_file() と同じですので、具体的な読み込み処理についてはそちらを参照してください。追加事項として、156 〜 173 行目にて PCM / 16 bits / Monoral の WAVE ファイルであるかどうかを確認し、そうでなければ処理失敗としています。また、208 〜 212 行目にて、読み込んだ 2 つの WAVE ファイルのサンプリングレートが一致しない場合も処理失敗としています。

  • 229 〜 270 行目 ( setup_dsp() )

    これまでのサンプルプログラムと同じ様に ioctl() で /dev/dsp の設定を行っています。LECTURE 10 WAVE ファイルを再生する のサンプルプログラム wave.c と異なり、245 〜 249 行目の ioctl( SNDCTL_DSP_SETFRAGMENT ) でフラグメントの設定を行っています。設定している値は 232 行目の 0x0004000c ですが、これはフラグメントのサイズを 4096 バイトとし、フラグメント数を 4 とする、という意味です( LECTURE 5 リアルタイム性を考慮する を参照してください )。

  • 273 〜 310 行目 ( show_menu() )

    main() の実行スレッドにて、ユーザ入力を取得するためのメニューを表示します。そして入力に応じて、ファイル冒頭で定義してあるマクロ COMMAND_XXXXX を WAVE 再生スレッド用の context 変数に設定します。ユーザが 99 を入力すると、ループを抜けて関数が終了します。

  • 313 〜 346 行目 ( play_thread() )

    WAVE ファイル再生スレッドの実行関数です。ループ処理ではまず再生用バッファを初期化し、ユーザ指定のコマンドに応じて処理を行います。322 〜 325 行目では、最初に指定した WAVE ファイルのデータを自作関数 fill_buffer() を使って再生用バッファに転送します。327 〜 330 行目では、2 番目に指定した WAVE ファイルについて同様に fill_buffer() を使って再生用バッファに転送します。fill_buffer() については後程説明します。332 〜 335 行目は、2 つの WAVE ファイルを自作関数 mix() で加算した結果を再生用バッファに転送します。mix() についても後程説明します。

    再生用バッファの内容を構築した後、342 行目で /dev/dsp に再生データを書き込んでいます。なお、ユーザによって COMMAND_STOP が指定されている場合も、無音状態のデータを書き込んでいます。

    ユーザによって COMMAND_EXIT が指定された場合はループを抜けて、345 行目の pthread_exit() で WAVE ファイル再生スレッドを終了しています。

    void pthread_exit( void* retval );
    retval: スレッドの終了コード
    
    
  • 349 〜 374 行目 ( fill_buffer() )

    指定バッファ ( dst ) に WAVE ファイルデータを転送します。

    WAVE ファイルデータの転送開始位置は wave->current で参照できます。355 〜 357 行目で、WAVE ファイルデータの残りと指定バッファの残りを比較し、一度に転送するバイト長を計算しています。WAVE ファイルデータの終端に到達した場合は、359 〜 363 行目で wave->current = 0; とすることで先頭に戻し、転送長の計算からやり直します。指定バッファ終端に到達している場合は、その時点で終了します(365 〜 367 行目)。

    一度の転送長を計算したら、369 行目の fread() で WAVE ファイルデータを指定バッファに転送します。fread() の第一引数(転送先指定)が dst + copied / sizeof( short ) の様になっているのは、dst が short* であるためアドレスが sizeof( short ) ずつ増分されてしまうからです。

  • 377 〜 378 行目 ( MIN(), MAX() )

    mix() で使用するマクロです。MIN() は小さい方の値を、MAX() は大きい方の値を返します。

  • 381 〜 430 行目 ( mix() )

    2 つの WAVE ファイルデータの加算を行います。WAVE ファイルデータ → src(関数内の自動変数)→ dst(指定バッファ)の順に転送を行います。まず 1 つめの WAVE ファイルデータで指定バッファを満たしてから、2 つめの WAVE ファイルデータを dst に転送しています。

    395 〜 397 行目で一度に転送するバイト長を計算しています。WAVE ファイルデータの終端に到達した場合は、399 〜 403 行目で wave->current = 0; とすることで先頭に戻し、転送長の計算からやりなおします。指定バッファ終端に到達している場合は、その時点で while() を抜けます(405 〜 407 行目。次の WAVE ファイルデータの処理に移るか、加算処理終了のいずれかとなる)。

    409 行目で fread() により WAVE ファイルデータを関数内の自動変数 src に転送します。dst ではなくまず src に転送するのは、ファイルから一気に読み込んだ上で加算処理を行いたいからです。411 〜 424 行目が実際の加算処理です。既に dst にあるデータと src を加算し、結果が SHRT_MIN より小さくなる場合は SHRT_MIN に、SHRT_MAX より大きくなる場合は SHRT_MAX で飽和させます(16 ビット符号付フォーマットのため)。

  • 433 〜 439 行目 ( shutdown() )

    リソース解放処理です。2 つの WAVE ファイルのファイルポインタを解放し、/dev/dsp も解放しています。


以上で mix.c の説明は終わりです。