戻る

エンベロープ

June 2, 2020

エンベロープをwavから抽出したり, 逆にbreakpointファイルからwavにエンベロープをかけて音量を調節したりなどをする.

エンベロープの抽出

モノラルのwavからサンプルデータがなすエンベロープを抽出しbreakpointファイルとして出力する. エンベロープは波形の大体の形を掴むもので, 15 mili secondごとにその時間内のサンプルデータのうち最大振幅のものを抽出して並べていく.


#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <portsf.h>

/* envx.c : 
 * extract amplitude envelope from mono soundfile*/
#define NFRAMES (1024)

/* TODO define program argument list, excluding flags */
enum {ARG_PROGNAME,ARG_INFILE,ARG_OUTFILE,ARG_NARGS};
#define DEFAULT_WINDOW_MSECS (15)

double maxsamp(float* buf, unsigned long blocksize){
  double absval, peak = 0.0;
  for(int i=0; i < blocksize; i++) {
    absval = fabs(buf[i]);
    if(absval > peak)
      peak = absval;
  }
  return peak;
}

int main(int argc, char* argv[])
{
/* STAGE 1 */	
	PSF_PROPS inprops,outprops;									
	long framesread;	
	/* init all dynamic resources to default states */
	int infile = -1,outfile = -1;
	int error = 0;
	PSF_CHPEAK* peaks = NULL;	
	psf_format outformat =  PSF_FMT_UNKNOWN;
	unsigned long nframes = NFRAMES;
	float* inframe = NULL;
	float* outframe = NULL;
	/* TODO: define an output frame buffer if channel width different 	*/
  double windur = DEFAULT_WINDOW_MSECS;
  unsigned long winsize;
	
/* STAGE 2 */	
	printf("ENVX: extract amplitude envelope from mono soundfile\n");						

	/* process any optional flags: remove this block if none used! */
	if(argc > 1){
		char flag;
		while(argv[1][0] == '-'){
			flag = argv[1][1];
			switch(flag){
			/*TODO: handle any  flag arguments here */
			case('\0'):
				printf("Error: missing flag name\n");
				return 1;
      case('w'):
        windur = atof(&argv[1][2]);
        if(windur<=0.0){
          printf("bad value for Window Duration."
              "Must be positive.\n");
          return 1;
        }
        break;
			default:
				break;
			}
			argc--;
			argv++;
		}
	}

	/* check rest of commandline */
	if(argc < ARG_NARGS){
		printf("insufficient arguments.\n"
			/* TODO: add required usage message */
			"usage: envx [-wN] insndfile outfile.brk\n"
      "-wN: set extraction window size to N msecs\n"
      "(default: 15)\n"
			);
		return 1;
	}
	/*  always startup portsf */
	if(psf_init()){
		printf("unable to start portsf\n");
		return 1;
	}
/* STAGE 3 */																							
	infile = psf_sndOpen(argv[ARG_INFILE],&inprops,0);															  
	if(infile < 0){
		printf("Error: unable to open infile %s\n",argv[ARG_INFILE]);
		error++;
		goto exit;
	}
	/* TODO: verify infile format for this application */
  if(inprops.chans > 1){
    printf("Soundfile contains %d channels: "
        "must be mono.\n",inprops.chans );
  }

  /* set  uffer size to the required envelope window size */
  windur /= 1000.0;
  winsize = (unsigned long)(windur * inprops.srate);

	inframe = (float*) malloc(winsize * sizeof(float));
	if(inframe==NULL){
		puts("No memory!\n");
		error++;
		goto exit;
	}
	/* check file extension of outfile name, so we use correct output file format*/
/* STAGE 4 */												
	/* TODO: any other argument processing and setup of variables, 		     
	   output buffer, etc., before creating outfile
	*/

	/* handle outfile */
	/* TODO:  make any changes to outprops here  */


	/* TODO: if outchans != inchans, allocate outframe, and modify main loop accordingly */

  FILE* fp = fopen(argv[ARG_OUTFILE], "w");
  if(fp == NULL){
    printf("envx: unable to create breakpoint file %s\n",
        argv[ARG_OUTFILE]);
    error++;
    goto exit;
  }
/* STAGE 5 */	
	printf("processing....\n");								
	/* TODO: init any loop-related variables */

  double amp=0;
  double brktime = 0.0;
  unsigned long npoints = 0;
	while ((framesread = psf_sndReadFloatFrames(infile,inframe,winsize)) > 0){
	
		/* <--------  add buffer processing here ------>  */
    amp = maxsamp(inframe, framesread);
    if(fprintf(fp, "%f\t%f\n",brktime,amp) < 2){
      printf("Failed to write to breakpoint file%s\n",argv[ARG_OUTFILE]);
      error++;
      break;
    }
    npoints++;
    brktime += windur;
	}

	if(framesread < 0)	{
		printf("Error reading infile. Outfile is incomplete.\n");
		error++;
	}
	else
		printf("Done: %d errors\n",error);
  printf("%ld breakpoints written to %s\n",
      npoints,argv[ARG_OUTFILE]);
/* STAGE 6 */		
	/* report PEAK values to user */							
	if(psf_sndReadPeaks(outfile,peaks,NULL) > 0){
		long i;
		double peaktime;
		printf("PEAK information:\n");	
			for(i=0;i < inprops.chans;i++){
				peaktime = (double) peaks[i].pos / (double) inprops.srate;
				printf("CH %ld:\t%.4f at %.4f secs\n", i+1, peaks[i].val, peaktime);
			}
	}
/* STAGE 7 */	
	/* do all cleanup  */    									
exit:	 	
	if(infile >= 0)
		if(psf_sndClose(infile))
			printf("%s: Warning: error closing infile %s\n",argv[ARG_PROGNAME],argv[ARG_INFILE]);
	if(outfile >= 0)
		if(psf_sndClose(outfile))
			printf("%s: Warning: error closing outfile %s\n",argv[ARG_PROGNAME],argv[ARG_OUTFILE]);
	if(inframe)
		free(inframe);
	/*TODO: cleanup any other resources */
  if(fp){
    if(fclose(fp)){
      printf("envx: failed to close output file %s\n" , argv[ARG_OUTFILE]);
    }
  }

	psf_finish();
	return error;
}

コンパイル, 使い方など


gcc -o envx.bin -lportsf envx.c
./envx.bin aaa.wav aaa.brk 

これによってaaa.wavと言うモノラルwavファイルからエンベロープを抽出する. デフォルトの15msごとに一つ値をとるのではなく, もっと細かくしたい場合は-wN でNにms単位でエンベロープ抽出の時間間隔を指定する. 次の例では2ms間隔でエンベロープ抽出, つまり1秒あたり500の分解能でエンベロープを抽出する. あまり細かすぎるとただの波形の絶対値を取っただけになりあまり意味がないのて匙加減が重要.

./envx.bin -w2 aaa.wav aaa.brk 

gnuplotをインストールしていればそのエンベロープのグラフを表示できる. これによりaaa.brkのグラフを表示, つまりエンベロープを実際に見ることができる.


gnuplot
gnuplot> plot [0:1][0:1] "aaa.brk"

エンベロープによる音量補正

breakpointファイルを読み込み線型補完でエンベロープを適用し音量を処理する. ステレオパンの記事と技術的にはあまり変わらないが, breakpointをサンプルごとにいちいち呼び出しせず次のbreakpointの時間になるまでサンプルデータを線型補完で処理する. 1フレームを読み込むことで1/サンプルレートの時間が経過するのでそれを積み重ね次のbreakpointの時間になったかを判定する.


/* sfenv.c */
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <portsf.h>
#include <breakpoints.h>

/* set size of multi-channel frame-buffer */
#define NFRAMES (1024)

/* TODO define program argument list, excluding flags */
enum {ARG_PROGNAME,ARG_INFILE,ARG_OUTFILE,ARG_BRKFILE,ARG_NARGS};

int main(int argc, char* argv[])
{
	PSF_PROPS inprops,outprops;														/* STAGE 1 */
	long framesread;	
	/* init all dynamic resources to default states */
	int ifd = -1,ofd = -1;
	int error = 0;
	PSF_CHPEAK* peaks = NULL;	
	psf_format outformat =  PSF_FMT_UNKNOWN;
	unsigned long nframes = NFRAMES;
	float* inframe = NULL;
	float* outframe = NULL;

	FILE* fp = NULL;
	unsigned long npoints;	
	BREAKPOINT* points = NULL;
	BREAKPOINT leftpoint,rightpoint;
	unsigned long  ileft,iright;
	double curpos,incr,width,height;
	int more_points;

	printf("SFENV: apply envelope to soundfile\n");									/* STAGE 2 */

	/* check rest of commandline */
	if(argc < ARG_NARGS){
		printf("insufficient arguments.\n"
			/* TODO: add required usage message */
			"usage: SFENV infile outfile envelope.brk\n"
			);
		return 1;
	}
	/*  always startup portsf */
	if(psf_init()){
		printf("unable to start portsf\n");
		return 1;
	}
																							
	ifd = psf_sndOpen(argv[ARG_INFILE],&inprops,0);									/* STAGE 3 */										  
	if(ifd < 0){
		printf("Error: unable to open infile %s\n",argv[ARG_INFILE]);
		error++;
		goto exit;
	}
	/* TODO: verify infile format for this application */
	if(inprops.chans != 1){
		printf("Error: only mono files supported\n");
		error++;
		goto exit;
	}
	/* allocate space for  sample frame buffer ...*/
	inframe = (float*) malloc(nframes * inprops.chans * sizeof(float));
	if(inframe==NULL){
		puts("No memory!\n");
		error++;
		goto exit;
	}
	/* check file extension of outfile name, so we use correct output file format*/
	outformat = psf_getFormatExt(argv[ARG_OUTFILE]);
	if(outformat == PSF_FMT_UNKNOWN){
		printf("outfile name %s has unknown format.\n"
			"Use any of .wav, .aiff, .aif, .afc, .aifc\n",argv[ARG_OUTFILE]);
		error++;
		goto exit;
	}
	inprops.format = outformat;
	outprops = inprops;
																					/* STAGE 4 */
	/* TODO: any other argument processing and setup of variables, 		     
	   output buffer, etc., before creating outfile
	*/

	/* read breakpoint file and verify it */
	fp = fopen(argv[ARG_BRKFILE],"r");
	if(fp == NULL){
		printf("Error: unable to open breakpoint file %s\n",argv[ARG_BRKFILE]);
		error++;
		goto exit;
	}
	npoints = 0;
	points = get_breakpoints(fp, &npoints);
	if(points==NULL){
		printf("No breakpoints read.\n");
		error++;
		goto exit;
	}
	if(npoints < 2){
		printf("Error: at least two breakpoints required\n");		
		free(points);
		fclose(fp);
		return 1;
	}	
	/* we require breakpoints to start from 0 */
	if(points[0].time != 0.0){
		printf("Error in breakpoint data: first time must be 0.0\n");		
		error++;
		goto exit;
	}	

	if(!inrange(points,0.0,1.0,npoints)){
		printf("Error in breakpoint file: values out of range -1 to +1 \n");
		error++;
		goto exit;
	}

	/* handle outfile */
	/* TODO:  make any changes to outprops here  */
	peaks  =  (PSF_CHPEAK*) malloc(outprops.chans * sizeof(PSF_CHPEAK));
	if(peaks == NULL){
		puts("No memory!\n");
		error++;
		goto exit;
	}

	/* TODO: if outchans != inchans, allocate outframe, and modify main loop accordingly */
	ofd = psf_sndCreate(argv[ARG_OUTFILE],&outprops,0,0,PSF_CREATE_RDWR);
	if(ofd < 0){
		printf("Error: unable to create outfile %s\n",argv[ARG_OUTFILE]);
		error++;
		goto exit;
	}

	printf("processing....\n");														/* STAGE 5 */

	/* init time position counter for reading envelope */
	incr = 1.0 / inprops.srate;
	/* setup counters to track through the breakpoint data */
	curpos = 0.0;
	ileft = 0;
	iright = 1;
	/* setup first span */
	leftpoint = points[ileft];
	rightpoint = points[iright];
	width = rightpoint.time - leftpoint.time;
	height = rightpoint.value - leftpoint.value;	
	more_points = 1;
	while ((framesread = psf_sndReadFloatFrames(ifd,inframe,nframes)) > 0){
		int i;
		double frac,thisamp;
		
		for(i=0; i < framesread;i++){
			if(more_points){
				if(width == 0.0)
					thisamp =  rightpoint.value;						
				else {
					/* not vertical: get interp value from this span */
					frac = (curpos - leftpoint.time) / width;
					thisamp = leftpoint.value + ( height * frac);
				}					
				/* move up ready for next sample */
				curpos += incr;
				if(curpos > rightpoint.time){			/*  need to go to next span? */
					ileft++; iright++;
					if(iright < npoints){						/* if we have another span, go there */
						leftpoint = points[ileft];
						rightpoint = points[iright];
						width = rightpoint.time - leftpoint.time;
						height = rightpoint.value - leftpoint.value;
					}
					else
						more_points = 0;
				}
			}
			inframe[i] = (float)(inframe[i] * thisamp);
		}
		if(psf_sndWriteFloatFrames(ofd,inframe,framesread) != framesread){
			printf("Error writing to outfile\n");
			error++;
			break;
		}	
	}

	if(framesread < 0)	{
		printf("Error reading infile. Outfile is incomplete.\n");
		error++;
	}
	else
		printf("Done: %d errors\n",error);
		
	/* report PEAK values to user */												/* STAGE 6 */
	if(psf_sndReadPeaks(ofd,peaks,NULL) > 0){
		long i;
		double peaktime;
		printf("PEAK information:\n");	
			for(i=0;i < inprops.chans;i++){
				peaktime = (double) peaks[i].pos / (double) inprops.srate;
				printf("CH %d:\t%.4f at %.4f secs\n", i+1, peaks[i].val, peaktime);
			}
	}	
	/* do all cleanup  */    														/* STAGE 7 */
exit:	 	
	if(ifd >= 0)
		psf_sndClose(ifd);
	if(ofd >= 0)
		psf_sndClose(ofd);
	if(inframe)
		free(inframe);
	if(peaks)
		free(peaks);
	/*TODO: cleanup any other resources */
	if(fp)
		fclose(fp);

	psf_finish();
	return error;
}

aaa.wavをenvelope.brkファイルで指定したエンベロープで処理しbbb.wavを出力する.


gcc -o sfenv.bin -lportsf -lbreakpoints sfenv.c
./sfenv.bin aaa.wav bbb.wav enelope.brk

音量調節と言うつまらない処理しかできないと思うかもしれないが, sin波などにもエンベロープをかけることで豊かな音を作ることができる.