annotate dsp/synth/sonify.m @ 61:eff6bddf82e3 tip

Finally implemented perceptual brightness thing.
author samer
date Sun, 11 Oct 2015 10:20:42 +0100
parents beb8a3f4a345
children
rev   line source
samer@34 1 function ad=sonify(S,D,varargin)
samer@34 2 % sonify - Sonify sequences of durations and pitches
samer@34 3 %
samer@34 4 % sonify ::
samer@34 5 % ([[N]] | seq(real)) ~'sequence of pitches in semitones',
samer@34 6 % (natural | seq(real)) ~'sequence of durations',
samer@34 7 % options {
samer@34 8 % env :: ([[N]]->[[N]]) ~'envelope shaping function',
samer@34 9 % transpose :: real /0 ~'add this to all pitches first';
samer@34 10 % bpm :: nonneg /240 ~'number of time units per minute';
samer@34 11 %
samer@34 12 % gen :: { 'sine' ~'sine wave',
samer@34 13 % 'square' ~'frequency-quantised square wave',
samer@34 14 % 'blsquare' ~'bandlimited square wave',
samer@34 15 % 'blsquare2' ~'alternative method',
samer@34 16 % 'shepard' ~'Shepard tones'
samer@34 17 % } / 'sine' ~'determines waveform generated';
samer@34 18 % betap :: [[2]] /[2,8] ~'Beta pdf parameters for Shepard tones'
samer@34 19 %
samer@34 20 % fs :: real /11025 ~'intended sampling rate';
samer@34 21 % buffer :: noneg /0.125 ~'buffer size in seconds, [] means one per note'
samer@34 22 % }
samer@34 23 % -> seq([[1,T]]).
samer@34 24
samer@37 25 opts=options( ...
samer@34 26 'bpm',240,'transpose',0, ...
samer@34 27 'gen','sine','betap',[2,8], ...
samer@34 28 'fs',11025, 'buffer',0.125, ...
samer@34 29 'env',[],'bl_cutoff',[], ...
samer@34 30 varargin{:});
samer@34 31
samer@34 32 env=opts.env;
samer@34 33 fm=440/opts.fs;
samer@34 34 dm=60*opts.fs/opts.bpm;
samer@34 35 tr=opts.transpose;
samer@34 36 num2hz=@(n)nan2x(0,fm*2.^((n+tr)/12));
samer@34 37
samer@34 38 if isscalar(D), D=dm*D;
samer@36 39 else D=map(@(d)dm*d,dd(D)); % D=dm*D with optimisation
samer@34 40 end
samer@36 41 C=map(@(n)(n+tr)/12,dd(S)); % chroma values
samer@36 42 F=map(num2hz,dd(S)); % convert to normalised frequencies
samer@34 43
samer@34 44 % cutoff for band-limited waveform generation
samer@34 45 if isempty(opts.bl_cutoff)
samer@34 46 cutoff=0.21+0.2*repeat(normalise01(cumsum(sample(stable(0,1.4,0),[1,1024]),2),2));
samer@34 47 else
samer@34 48 cutoff=opts.bl_cutoff;
samer@34 49 end
samer@34 50
samer@34 51 switch opts.gen
samer@34 52 case 'square'
samer@34 53 % square wave with period quantized to sample period
samer@34 54 Y=blockdata(square,D,0,repeat(0.5),F);
samer@34 55 case 'blsquare'
samer@34 56 % cumsum within each buffer: doesn't drift but makes
samer@34 57 % audible clicks between buffers.
samer@36 58 Y=map(@(t)0.5*cumsum(t),blockdata(bpblit,D,0,cutoff,F));
samer@34 59 case 'blsquare2'
samer@34 60 % this version does cumsum across the sequence to remove
samer@34 61 % discontinuities but requires high-pass filter to stop drift.
samer@34 62 [fb,fa]=butter(2,0.001,'high');
samer@34 63 Y=filter(0.5*fb,fa,cumsum(blockdata(bpblit,D,0,cutoff,F),2));
samer@34 64 case 'shepard'
samer@34 65 Y=0.04*shepardtone(D,C,opts.betap);
samer@34 66 case 'sine2'
samer@34 67 Y=blockdata(sine,D,0,F)./(F/0.01);
samer@34 68 otherwise
samer@34 69 Y=blockdata(sine,D,0,F);
samer@34 70 end
samer@34 71
samer@34 72 % if supplied apply envelope to shape each note
samer@34 73 if nargin>2 && ~isempty(env),
samer@36 74 ad=map(env,Y);
samer@34 75 else
samer@34 76 ad=Y;
samer@34 77 end
samer@34 78
samer@34 79 % rebuffer to constant size
samer@34 80 if ~isempty(opts.buffer)
samer@36 81 ad=window(ad,ceil(opts.buffer*opts.fs));
samer@34 82 end
samer@34 83