SpectrumAnalyser.csd
Written by Iain McCurdy, 2011

<CsoundSynthesizer>

<CsOptions>
-odac -iadc -dm0
</CsOptions>

<CsInstruments>

sr 		= 	44100	;SAMPLE RATE
ksmps 		= 	8	;NUMBER OF AUDIO SAMPLES IN EACH CONTROL CYCLE
nchnls 		= 	2	;NUMBER OF CHANNELS (2=STEREO)
0dbfs		=	1	;MAXIMUM AMPLITUDE

;FLTK INTERFACE CODE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
FLcolor	255, 255, 255, 250, 250, 50	;SET INTERFACE COLOURS
;			LABEL               | WIDTH | HEIGHT | X | Y
		FLpanel	"Spectrum Analyser",  1010,    780,    0,  0
;SWITCHES                       					ON | OFF | TYPE | WIDTH | HEIGHT | X | Y | OPCODE | INS | STARTTIM | IDUR
gkAnaOnOff,ihAnaOnOff	FLbutton	"Analyser On/Off",		1,    0,    22,    180,     30,    5,  5,    -1

;TEXT BOXES					TYPE | FONT | SIZE | WIDTH | HEIGHT | X | Y
ih	 	FLbox  	"", 			6,      5,     14,    1006,   115,    2, 410
FLsetColor	255,255,230,ih
ih	 	FLbox  	"Captured Waveform. GEN 10 Partial Strengths.", 	1,      3,     14,     340,    20,   10, 413
FLsetColor	255,255,230,ih

#define	CHANNEL(N)
#
;SLIDERS					MIN |  MAX | EXP | TYPE |  DISP | WIDTH | HEIGHT | X   | Y
gk$N, gih$N		FLslider	"$N",	0,      1,    0,     2,     -1, iwidth,   250,    ix,   55	;ANALYSER CHANNEL BAR DISPLAY OF RMS FOR THAT CHANNEL
FLsetColor	255,255,50,gih$N	;SET PRIMARY COLOUR OF ANALYSER BARS TO YELLOW
FLsetColor2	255,255,255,gih$N	;SET SECONDARY COLOUR OF ANALYSER BARS TO WHITE
;TEXT BOXES				TYPE | FONT | SIZE | WIDTH | HEIGHT | X | Y
gihlbl$N	 	FLbox  	"", 	1,      1,     10,    iwidth,    15,     ix,  320			;BOX USED TO DISPLAY THE FREQUENCY OF THE ANALYSER BAND. THIS LABEL IS UPDATED FROM WITHIN THE ORCHESTRA WHENEVER THE FUNDAMENTAL IS CHANGED.
;VALUE BOXES					MIN | MAX | STEP | TYPE | WIDTH | HEIGHT | X  | Y
gktxt$N, gihtxt$N	FLtext		"", 	0,     1,     0.001,     1,  iwidth,    20,   ix,  335		;DISPLAY OF VALUES GENERATED BY THE SPECTRUM ANALYSER
gkval$N, gihval$N	FLtext		"$N", 	0,     1,     0.001,     1,  iwidth,    20,   ix,  440		;PARTIAL STRENGTHS IN THE STORED GEN10 FUNCTION TABLE
ix	=	ix + iwidth
FLsetVal_i	1,gih$N
#

ix	=	5	;X-LOCATION OF FIRST ANALYSER CHANNEL
iwidth	=	40	;WIDTH OF ANALYSER CHANNEL

$CHANNEL(1)
$CHANNEL(2)
$CHANNEL(3)
$CHANNEL(4)
$CHANNEL(5)                                
$CHANNEL(6)
$CHANNEL(7)
$CHANNEL(8)
$CHANNEL(9)
$CHANNEL(10)
$CHANNEL(11)
$CHANNEL(12)
$CHANNEL(13)
$CHANNEL(14)
$CHANNEL(15)
$CHANNEL(16)
$CHANNEL(17)
$CHANNEL(18)
$CHANNEL(19)
$CHANNEL(20)
$CHANNEL(21)
$CHANNEL(22)
$CHANNEL(23)
$CHANNEL(24)
$CHANNEL(25)

;VALUE INPUT BOXES				 		MIN | MAX | STEP | TYPE | WIDTH | HEIGHT | X  | Y
gkfundHz, gihfundHz		FLtext		"Fund.(Hz)", 	20,  2000,  0.01,   1,   100,     20,     10,  370
gkfundNum, gihfundNum		FLtext		"Fund.(Num)", 	1,    127,  0.01,   1,   100,     20,    120,  370
gkbw, ihbw			FLtext		"Bandwidth", 	0.01,   1,  0.001,  1,   100,     20,    230,  370
gkpregain, ihpregain		FLtext		"Pre-gain", 	0,     50,  0.01,   1,   100,     20,    340,  370
gkpostgain, ihpostgain		FLtext		"Post-gain", 	0,     10,  0.01,   1,   100,     20,    450,  370
gksmoothing, ihsmoothing	FLtext		"Smoothing", 	0,    0.1,  0.01,   1,   100,     20,    560,  370

;SWITCHES                       					ON | OFF | TYPE | WIDTH | HEIGHT | X | Y | OPCODE | INS | STARTTIM | IDUR
gkWriteTable,ihWriteTable	FLbutton	"Write Table",		1,    0,    21,    130,     25,  670, 370,    0,      2,      0,       0
gkDumpTable,ihDumpTable		FLbutton	"Dump Table",		1,    0,    21,    130,     25,  810, 370,    0,      4,      0,       0.001


;SWITCHES                       					ON | OFF | TYPE | WIDTH | HEIGHT | X | Y | OPCODE | INS | STARTTIM | IDUR
gkPlayTone,ihPlayTone		FLbutton	"Play Tone",		1,    0,    22,    130,     25,   10, 480,    0,      3,      0,       -1

;SLIDERS							MIN |  MAX | EXP | TYPE |  DISP | WIDTH | HEIGHT | X   | Y
gkToneVol, ihToneVol		FLslider	"Volume",	-98,     0,    0,    23,     -1,   200,    25,    150,  480	;

; INITIALISATION OF VALUATORS	VALUE          | HANDLE
		FLsetVal_i	cpsmidinn(60), 	gihfundHz
		FLsetVal_i	60,             gihfundNum
		FLsetVal_i	0.05, 	        ihbw
		FLsetVal_i	1, 	        ihpregain
		FLsetVal_i	0, 	        ihpostgain                                                                                     
		FLsetVal_i	0.04, 	        ihsmoothing
		FLsetVal_i	-40, 	        ihToneVol
		FLscroll	1010,240,0,530
;TEXT BOXES																		TYPE | FONT | SIZE | WIDTH | HEIGHT | X | Y
ih	 	FLbox  	"                                     Spectrum Analyser - GEN 10 Generator                                               ",	1,      5,     14,     980,    20,   10, 530
ih	 	FLbox  	"------------------------------------------------------------------------------------------------------------------------", 	1,      5,     14,     980,    20,   10, 550
ih	 	FLbox  	"This is example implements a simple harmonic spectrum analyser with the intention that the analysed spectrum can then be", 	1,      5,     14,     980,    20,   10, 570
ih	 	FLbox  	"written to a GEN 10 function for use in a subsequent synthesizer.                                                       ", 	1,      5,     14,     980,    20,   10, 590
ih	 	FLbox  	"The centre frequencies of the 25 analysis bands correspond the the first 25 partials of a harmonic stack built upon the ", 	1,      5,     14,     980,    20,   10, 610
ih	 	FLbox  	"user defined fundamental. Fundamental can be defined either in hertz or in note number format. If 'Fundamental' is      ", 	1,      5,     14,     980,    20,   10, 630
ih	 	FLbox  	"changed the frequencies of the analysis band are adjusted automatically. The input sound for analysis should sustain a  ", 	1,      5,     14,     980,    20,   10, 650
ih	 	FLbox  	"note at a pitch corresponding to the fundamental frequency. For an audio hint of this click the 'Play Tone' button. This", 	1,      5,     14,     980,    20,   10, 670
ih	 	FLbox  	"plays a tone using the waveform based on the most recently captured sound spectrum. The default waveform (i.e. before   ", 	1,      5,     14,     980,    20,   10, 690
ih	 	FLbox  	"one has been created) is based on an analysis using this example of mesinging 'ahh'. 'Fundamental' can of course also be", 	1,      5,     14,     980,    20,   10, 710
ih	 	FLbox  	"adjusted to the input sound, the pitch of the playback tone will of course reflect changes made to 'Fundamental'.       ", 	1,      5,     14,     980,    20,   10, 730
ih	 	FLbox  	"'Pre-gain' controls the gain applied to audio before it enters the analyser. Whilst it is not crucial for partial meters", 	1,      5,     14,     980,    20,   10, 750
ih	 	FLbox  	"to maintain high levels, it is important that they aren't hitting their maximums when an analysis is made if an accurate", 	1,      5,     14,     980,    20,   10, 770
ih	 	FLbox  	"measurement is to be made. Bandwidth defines the tolerance of the analysis bands: too narrow a bandwidth and it will be ", 	1,      5,     14,     980,    20,   10, 790
ih	 	FLbox  	"difficult to hold a pitch steady enough to accurately stay within the analyser bands, too wide a bandwidth and the      ", 	1,      5,     14,     980,    20,   10, 810
ih	 	FLbox  	"spectrum analysis will not reflect the input spectrum adequately. The default setting should work well with most inputs.", 	1,      5,     14,     980,    20,   10, 830
ih	 	FLbox  	"'Post-gain' controls the level of input signal send straight to the output. During analysis this should be at zero but  ", 	1,      5,     14,     980,    20,   10, 850
ih	 	FLbox  	"it may be useful to be able to monitor audio during setting up.                                                         ", 	1,      5,     14,     980,    20,   10, 870
ih	 	FLbox  	"Once a stable pitch that corresponds the the fundemental is being played into the analyser its spectral envelope can be ", 	1,      5,     14,     980,    20,   10, 890
ih	 	FLbox  	"captured by clicking the 'Write Table' button. To audition this analysis as a resynthesis click 'Play Tone'.            ", 	1,      5,     14,     980,    20,   10, 910
ih	 	FLbox  	"A captured analysis can be written to a text file by clicking 'Dump Table'. In the text file this is formatted as a GEN ", 	1,      5,     14,     980,    20,   10, 930
ih	 	FLbox  	"10 function table ready for insertion into another Csound orchestra. If the 'Dump Table' button is clicked again the new", 	1,      5,     14,     980,    20,   10, 950
ih	 	FLbox  	"table is appended to the one already written to the text file, separated by a blank line. Previously written tables are ", 	1,      5,     14,     980,    20,   10, 970
ih	 	FLbox  	"not overwritten.                                                                                                        ", 	1,      5,     14,     980,    20,   10, 990
		FLscroll_end
		FLpanel_end	;END OF PANEL CONTENTS                                                                                                            

;INSTRUCTIONS AND INFO PANEL

				FLrun	;RUN THE FLTK WIDGET THREAD
;END OF FLTK INTERFACE CODE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;STORED GEN10 WAVEFORM (DEFAULTS TO AN 'AHH' SOUND)
giwave	ftgen	2, 0, 4096, 10, 0.615880,0.089582,0.201498,0.202694,0.249961,0.225852,0.199864,0.029378,0.012306,0.007803,0.007846,0.007454,0.008827,0.007466,0.007495,0.007979,0.017837,0.061000,0.059055,0.039181,0.038364,0.032715,0.048415,0.029643,0.010094
;GEN 2 LIST OF THE VALUES NEEDED TO RECREATE THE GEN 10 WAVEFORM. IT IS THIS TABLE THAT CAN BE WRITTEN TO A TEXT FILE.                                                                                                                        
givals	ftgen	3, 0, -25,   -2, 0.615880,0.089582,0.201498,0.202694,0.249961,0.225852,0.199864,0.029378,0.012306,0.007803,0.007846,0.007454,0.008827,0.007466,0.007495,0.007979,0.017837,0.061000,0.059055,0.039181,0.038364,0.032715,0.048415,0.029643,0.010094

instr	1	;SPECTRUM ANALYSER
	;--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
	; SYNCHRONIZE FUNDAMENTAL HERTZ/NOTE NUMBER FORMAT INPUTS 
	;--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------	
	koldHz	init	i(gkfundHz)				;INITIALISE OLD VALUE OF FUNDAMENTAL IN HERTZ FOR FIRST PASS
	koldNum	init	i(gkfundNum)				;INITIALISE OLD VALUE OF FUNDAMENTAL AS A NOTE NUMBER FOR FIRST PASS
	if koldHz!=gkfundHz then				;IF OLD VALUE FOR FUNDAMENTAL IN HERTZ IS NOT THE SAME AS THE NEW VALUE...
	  gkNum	=	(octcps(gkfundHz)-3)*12			;...DERIVE A NEW VALUE FOR FUNDAMENTAL AS A NOTE NUMBER FROM NEW VALUE 
		FLsetVal	1,gkNum,gihfundNum		;SET FUNDAMENTAL (NOTE NUMBER FORMAT) VALUE BOX BASED ON FUNDAMENTAL (HERTZ) VALUE
	elseif koldNum!=gkfundNum then				;IF OLD VALUE FOR FUNDAMENTAL AS A NOTE NUMBER IS NOT THE SAME AS THE NEW VALUE...
	  gkFundHz	=	cpsmidinn(gkfundNum)		;...DERIVE A NEW VALUE FOR FUNDAMENTAL IN HERTZ FROM NEW VALUE FOR FUNDMANETAL AS A NOTE NUMBER
		FLsetVal	1,gkFundHz,gihfundHz		;SET FUNDAMENTAL (IN HERTZ) VALUE BOX BASED ON FUNDAMENTAL (NOTE NUMBER) VALUE
	endif
	koldHz	=	gkfundHz				;SET FUNDAMENTAL IN HERTZ 'OLD VALUE' FOR NEXT PASS
	koldNum	=	gkfundNum				;SET FUNDAMENTAL AS A NOTE NUMBER 'OLD VALUE' FOR NEXT PASS
	
	;--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
	; READ IN AN AUDIO INPUT (LIVE AUDIO IN) 
	;--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------	
	asig	inch		1
	;OR USE A SOUND FILE IF YOU PREFER...
	;asig,aR	diskin	"OneNoteSoundfile.wav",1,0,1

	asig	=		asig * gkpregain	;RESCALE AUDIO BEFORE ANALYSER WITH PRE-GAIN CONTROL

	kmetro	metro		15			;METRONOME THAT TRIGGERS UPDATES OF SLIDERS

	;--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
	; CREATE I-RATE VERSION OF FUNDAMENTAL AND BANDWIDTH TO IMPROVE REAL-TIME EFFICIENCY 
	;--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------	
	ktrig	changed	gkfundHz,gkbw			;IF FUNDAMENTAL OR BANDWIDTH ARE CHANGED GENERATE A TRIGGER IMPULSE
	if ktrig==1 then				
	  reinit	UPDATE				;REINIT FROM 'UPDATE' WHENEVER FUNDAMENTAL OR BANDWIDTH ARE CHANGED
	endif
	UPDATE:
	ibw	=		i(gkbw)		;BANDWIDTH AS A FRACTION OF THE BAND FREQUENCY
	iFund	=		i(gkfundHz)	;FUNDAMENTAL FREQUENCY

	;--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
	; DEFINE A MACRO FOR THE CODE FOR AN ANALYSIS BAND 
	;--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------	
#define	BAND(N)
	#
	iFrq$N	=		iFund*$N
	Sfrq$N	sprintf		"%dHz",iFrq$N			;FREQUENCY LABEL DISPLAY ROUNDED TO THE NEAREST INTEGER
		FLsetText	Sfrq$N,gihlbl$N			;PRINT FREQUENCY OF THIS ANALYSER BAND TO THE GUI
	if gkAnaOnOff==1 then					;IF SPECTRUM ANALYSER ON/OFF SWITCH IS ON...
	  if iFrq$N>=sr/2 then					;IF FREQUENCY IS BEYOND THE NYQUIST FREQUENCY...
	    k$N = 0						;SET AMPLITUDE FOR THIS PARTIAL TO ZERO (TO PREVERT ALIASING)
	    goto SKIP$N						;JUMP TO LABEL 'SKIP'
	  endif
	  ab$N	butbp		asig, iFrq$N, iFrq$N * ibw	;...BANDPASS FILTER INPUT AUDIO SIGNAL
	  ab$N	butbp		ab$N, iFrq$N, iFrq$N * ibw	;BANDPASS FILTERING IS REPEATED IN ORDER TO SHARPEN CUTOFF SLOPES
	  k$N	rms		ab$N				;SCAN RMS OF ANALYSIS BAND
	  k$N	portk		k$N,gksmoothing			;SMOOTH RMS SIGNAL
	  SKIP$N:
		FLsetVal	kmetro,1-k$N,gih$N		;UPDATE METER FOR THIS ANALYSIS BAND
		FLsetVal	kmetro,k$N,gihtxt$N		;UPDATE TEXT BOX FOR THIS ANALYSIS BAND
	endif
	#
	;--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
	; EXPAND MACRO FOR EACH BAND REQUIRED 
	;--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------	
	$BAND(1)
	$BAND(2)
	$BAND(3)
	$BAND(4)
	$BAND(5)
	$BAND(6)
	$BAND(7)                              
	$BAND(8)
	$BAND(9)
	$BAND(10)
	$BAND(11)
	$BAND(12)
	$BAND(13)
	$BAND(14)
	$BAND(15)
	$BAND(16)
	$BAND(17)
	$BAND(18)
	$BAND(19)
	$BAND(20)
	$BAND(21)
	$BAND(22)
	$BAND(23)
	$BAND(24)
	$BAND(25)
		outs		asig*gkpostgain,asig*gkpostgain	
endin

instr	2       ;WRITE SNAPSHOT OF WAVEFORM SCANNED BOTH AS A GEN 10 HARMONIC WAVEFORM AND A GEN 2 LIST OF VALS
	giwave	ftgen	2, 0, 4096, 10, 1-i(gk1), 1-i(gk2), 1-i(gk3), 1-i(gk4), 1-i(gk5), 1-i(gk6), 1-i(gk7), 1-i(gk8), 1-i(gk9), 1-i(gk10), 1-i(gk11), 1-i(gk12), 1-i(gk13), 1-i(gk14), 1-i(gk15), 1-i(gk16), 1-i(gk17), 1-i(gk18), 1-i(gk19), 1-i(gk20), 1-i(gk21), 1-i(gk22), 1-i(gk23), 1-i(gk24), 1-i(gk25) 	
	givals	ftgen	3, 0,  -25, -2, 1-i(gk1), 1-i(gk2), 1-i(gk3), 1-i(gk4), 1-i(gk5), 1-i(gk6), 1-i(gk7), 1-i(gk8), 1-i(gk9), 1-i(gk10), 1-i(gk11), 1-i(gk12), 1-i(gk13), 1-i(gk14), 1-i(gk15), 1-i(gk16), 1-i(gk17), 1-i(gk18), 1-i(gk19), 1-i(gk20), 1-i(gk21), 1-i(gk22), 1-i(gk23), 1-i(gk24), 1-i(gk25)
		event_i	"i",5,0,0	;UPDATE FLtext BOXES DISPLAY OF PARTIAL STRENGTHS IN STORED GEN10 FUNCTION TABLE
endin

instr	3	;PLAY TONE BASED ON STORED GEN10 TABLE
	kporttime	linseg	0,0.01,0.01			;PORTAMENTO FUNCTION RAMPS UP QUICKLY FROM ZERO TO A FIXED VALUE
	kToneVol	portk	gkToneVol,kporttime		;APPLY PORTAMENTO TO FLTK AUDIO TONE VOLUME CONTROL
	if gkPlayTone==0 then
	  turnoff
	endif
	asig	poscil	ampdbfs(kToneVol),gkfundHz,2		;GENERATE AN AUDIO TONE
		outs	asig,asig				;SEND TO OUTPUTS
endin

instr	4	;DUMP TABLE TO A TEXT FILE ON DISK
	; - TEXT WILL BE FORMATTED AS A GEN 10 FUNCTION TABLE READY TO BE COPY-AND-PASTED INTO A NEW ORCHESTRA 
	;READ ALL PARTIAL STRENGTH VALUES FROM GEN 2 TABLE ONE BY ONE
	ival1	table	0,givals
	ival2	table	1,givals
	ival3	table	2,givals
	ival4	table	3,givals
	ival5	table	4,givals
	ival6	table	5,givals
	ival7	table	6,givals
	ival8	table	7,givals
	ival9	table	8,givals
	ival10	table	9,givals
	ival11	table	10,givals
	ival12	table	11,givals
	ival13	table	12,givals
	ival14	table	13,givals
	ival15	table	14,givals
	ival16	table	15,givals
	ival17	table	16,givals
	ival18	table	17,givals
	ival19	table	18,givals
	ival20	table	19,givals
	ival21	table	20,givals
	ival22	table	21,givals
	ival23	table	22,givals
	ival24	table	23,givals
	ival25	table	24,givals
	;WRITE TEXT FOR A GEN10 FUNCTION TABLE
	fprints "PartialStrengths.txt", "giwave ftgen 0,0,4096,10,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f%n%n",ival1,ival2,ival3,ival4,ival5,ival6,ival7,ival8,ival9,ival10,ival11,ival12,ival13,ival14,ival15,ival16,ival17,ival18,ival19,ival20,ival21,ival22,ival23,ival24,ival25
endin

instr	5	;WRITE PARTIAL STRENGTHS OF STORED GEN10 TABLE TO FLtext BOXES
	;DEFINE A MACRO
#define	PRINT_VAL(N)
	#
	ival$N		table	$N-1,givals
	FLsetVal_i	ival$N,gihval$N
	#
	;EXPAND MACRO FOR EACH ANALYSIS BAND
	$PRINT_VAL(1)
	$PRINT_VAL(2)
	$PRINT_VAL(3)
	$PRINT_VAL(4)
	$PRINT_VAL(5)
	$PRINT_VAL(6)
	$PRINT_VAL(7)
	$PRINT_VAL(8)
	$PRINT_VAL(9)
	$PRINT_VAL(10)
	$PRINT_VAL(11)
	$PRINT_VAL(12)
	$PRINT_VAL(13)
	$PRINT_VAL(14)
	$PRINT_VAL(15)
	$PRINT_VAL(16)
	$PRINT_VAL(17)
	$PRINT_VAL(18)
	$PRINT_VAL(19)
	$PRINT_VAL(20)
	$PRINT_VAL(21)
	$PRINT_VAL(22)
	$PRINT_VAL(23)
	$PRINT_VAL(24)
	$PRINT_VAL(25)	
endin

</CsInstruments>

<CsScore>
i 1 0 3600	;RUN SPECTRUM ANALYSER
i 5 0 0		;WRITE DATA IN STORED GEN10 FUNCTION TABLE TO FLtext BOXES WHEN PERFORMANCE STARTS
</CsScore>

</CsoundSynthesizer>
