tomwalters@0
|
1 % tool
|
tomwalters@0
|
2 %
|
tomwalters@0
|
3 % INPUT VALUES:
|
tomwalters@0
|
4 %
|
tomwalters@0
|
5 % RETURN VALUE:
|
tomwalters@0
|
6 %
|
tomwalters@0
|
7 %
|
bleeck@3
|
8 % (c) 2011, University of Southampton
|
bleeck@3
|
9 % Maintained by Stefan Bleeck (bleeck@gmail.com)
|
bleeck@3
|
10 % download of current version is on the soundsoftware site:
|
bleeck@3
|
11 % http://code.soundsoftware.ac.uk/projects/aimmat
|
bleeck@3
|
12 % documentation and everything is on http://www.acousticscale.org
|
bleeck@3
|
13
|
tomwalters@0
|
14
|
tomwalters@0
|
15 function [newsig,strobe_points,threshold]=adaptivthreshold(sig,options)
|
tomwalters@0
|
16
|
tomwalters@0
|
17 if nargin < 2
|
tomwalters@0
|
18 options=[];
|
tomwalters@0
|
19 end
|
tomwalters@0
|
20
|
tomwalters@0
|
21 % the way, in which the strobes are calculated
|
tomwalters@0
|
22 if isfield(options,'strobe_decay_time')
|
tomwalters@0
|
23 strobe_decay_time=options.strobe_decay_time;
|
tomwalters@0
|
24 else
|
tomwalters@0
|
25 strobe_decay_time=0.04;
|
tomwalters@0
|
26 end
|
tomwalters@0
|
27
|
tomwalters@0
|
28 % the time constant of the strobe decay
|
tomwalters@0
|
29 if isfield(options,'strobe_decay_time')
|
tomwalters@0
|
30 strobe_decay_time=options.strobe_decay_time;
|
tomwalters@0
|
31 else
|
tomwalters@0
|
32 strobe_decay_time=0.04;
|
tomwalters@0
|
33 end
|
tomwalters@0
|
34
|
tomwalters@0
|
35 % the timeout, after that time a strobe must occure
|
tomwalters@0
|
36 if isfield(options,'timeout')
|
tomwalters@0
|
37 timeout=options.timeout;
|
tomwalters@0
|
38 else
|
tomwalters@0
|
39 strobe_decay_time=0.01;
|
tomwalters@0
|
40 end
|
tomwalters@0
|
41
|
tomwalters@0
|
42 % the strobe lag, ie the time that is waited for the next strobe to occure
|
tomwalters@0
|
43 if isfield(options,'strobe_lag')
|
tomwalters@0
|
44 strobe_lag=options.strobe_lag;
|
tomwalters@0
|
45 else
|
tomwalters@0
|
46 strobe_lag=0.005;
|
tomwalters@0
|
47 end
|
tomwalters@0
|
48
|
tomwalters@0
|
49 % the threshold of a strobe
|
tomwalters@0
|
50 if isfield(options,'threshold')
|
tomwalters@0
|
51 threshold=options.threshold;
|
tomwalters@0
|
52 else
|
tomwalters@0
|
53 threshold=0.04;
|
tomwalters@0
|
54 end
|
tomwalters@0
|
55
|
tomwalters@0
|
56 % if we want to start with a different threshold
|
tomwalters@0
|
57 if isfield(options,'startingthreshold')
|
tomwalters@0
|
58 current_threshold=options.startingthreshold;
|
tomwalters@0
|
59 else
|
tomwalters@0
|
60 current_threshold=0;
|
tomwalters@0
|
61 end
|
tomwalters@0
|
62
|
tomwalters@0
|
63 % if some graphic should be displayed during run
|
tomwalters@0
|
64 if isfield(options,'grafix')
|
tomwalters@0
|
65 grafix=options.grafix;
|
tomwalters@0
|
66 else
|
tomwalters@0
|
67 grafix=0; %
|
tomwalters@0
|
68 end
|
tomwalters@0
|
69
|
tomwalters@0
|
70 % % if the result shell be filtered by this lowpass filter:
|
tomwalters@0
|
71 % if isfield(options,'lowpass')
|
tomwalters@0
|
72 % lowpassvalue=options.lowpass;
|
tomwalters@0
|
73 % else
|
tomwalters@0
|
74 % lowpassvalue=-1; %
|
tomwalters@0
|
75 % end
|
tomwalters@0
|
76
|
tomwalters@0
|
77 sr=getsr(sig);
|
tomwalters@0
|
78 newsig=sig;
|
tomwalters@0
|
79 newsig=setname(newsig,sprintf('adaptive threshold of %s',getname(sig)));
|
tomwalters@0
|
80 % newvals=getvalues(sig);
|
tomwalters@0
|
81 threshold=sig;
|
tomwalters@0
|
82 tresval=getvalues(sig);
|
tomwalters@0
|
83
|
tomwalters@0
|
84
|
tomwalters@0
|
85 sigvals=getvalues(sig);
|
tomwalters@0
|
86 options.current_threshold=current_threshold;
|
tomwalters@0
|
87 options.sr=sr;
|
tomwalters@0
|
88
|
tomwalters@0
|
89 switch options.strobe_criterion
|
tomwalters@0
|
90 case 'parabola'
|
tomwalters@0
|
91 [strobe_points,tresval,newvals]=doadaptivethresholdparabola(sigvals,options);
|
tomwalters@0
|
92 case 'threshold'
|
tomwalters@0
|
93 case 'peak'
|
tomwalters@0
|
94 [strobe_points,tresval,newvals]=doadaptivethresholdpeak(sigvals,options);
|
tomwalters@0
|
95 case 'peak_shadow+'
|
tomwalters@0
|
96 [strobe_points,tresval,newvals]=doadaptivethresholdpeakshadowplus(sigvals,options);
|
tomwalters@0
|
97 case 'peak_shadow-'
|
tomwalters@0
|
98 case 'delta_gamma'
|
tomwalters@0
|
99 case 'adaptive'
|
tomwalters@0
|
100 [strobe_points,tresval,newvals]=doadaptivethresholdadaptive(sigvals,options);
|
tomwalters@0
|
101 case 'bunt'
|
tomwalters@0
|
102 [strobe_points,tresval,newvals]=doadaptivethresholdbunt(sigvals,options);
|
tomwalters@0
|
103 end
|
tomwalters@0
|
104
|
tomwalters@0
|
105 strobe_points=bin2time(sig,strobe_points);
|
tomwalters@0
|
106
|
tomwalters@0
|
107 newsig=setvalues(newsig,newvals);
|
tomwalters@0
|
108 threshold=setvalues(threshold,tresval);
|
tomwalters@0
|
109
|
tomwalters@0
|
110 % % do a low pass filtering of the result to smooth it
|
tomwalters@0
|
111 % if lowpassvalue > -1
|
tomwalters@0
|
112 % if lowpassvalue > 2000
|
tomwalters@0
|
113 % lowpassvalue=2000;
|
tomwalters@0
|
114 % end
|
tomwalters@0
|
115 % newsig=lowpass(newsig,lowpassvalue);
|
tomwalters@0
|
116 % newsig=halfwayrectify(newsig);
|
tomwalters@0
|
117 % end
|
tomwalters@0
|
118
|
tomwalters@0
|
119
|
tomwalters@0
|
120 if grafix
|
tomwalters@0
|
121 clf
|
tomwalters@0
|
122 plot(sig); hold on
|
tomwalters@0
|
123 ax=axis;
|
tomwalters@0
|
124 plot(newsig,'r');
|
tomwalters@0
|
125 plot(threshold,'g');
|
tomwalters@0
|
126 axis(ax);
|
tomwalters@0
|
127 end
|
tomwalters@0
|
128 return
|
tomwalters@0
|
129
|
tomwalters@0
|
130
|
tomwalters@0
|
131 function [strobe_points,tresval,newvals]=doadaptivethresholdparabola(sigvals,options)
|
tomwalters@0
|
132 % the threshold is calculated relativ to the hight of the last strobe
|
tomwalters@0
|
133 % the sample rate is needed for the calculation of the next thresholds
|
tomwalters@0
|
134 % for time_constant_alpha this is a linear decreasing function that goes
|
tomwalters@0
|
135 % from the maximum value to 0 in the time_constant
|
tomwalters@0
|
136
|
tomwalters@0
|
137 current_threshold=options.current_threshold;
|
tomwalters@0
|
138 sr=options.sr;
|
tomwalters@0
|
139 last_strobe=-inf;
|
tomwalters@0
|
140
|
tomwalters@0
|
141 tresval=zeros(size(sigvals));
|
tomwalters@0
|
142 newvals=zeros(size(sigvals));
|
tomwalters@0
|
143 nr=length(sigvals);
|
tomwalters@0
|
144 strobe_points=zeros(100,1);
|
tomwalters@0
|
145
|
tomwalters@0
|
146 %when the last strobe occured
|
tomwalters@0
|
147 % last_strobe=-inf;
|
tomwalters@0
|
148 last_threshold_value=0;
|
tomwalters@0
|
149
|
tomwalters@0
|
150 % copy options to save time
|
tomwalters@0
|
151 h=options.parabel_heigth;
|
tomwalters@0
|
152 w=options.parabel_width;
|
tomwalters@0
|
153 % w=options.parabel_width_in_cycles*1/options.current_cf;
|
tomwalters@0
|
154 strobe_decay_time=options.strobe_decay_time;
|
tomwalters@0
|
155
|
tomwalters@0
|
156 counter=1;
|
tomwalters@0
|
157 for ii=1:nr
|
tomwalters@0
|
158 current_val=sigvals(ii);
|
tomwalters@0
|
159 current_time=ii/sr;
|
tomwalters@0
|
160
|
tomwalters@0
|
161 if current_val>current_threshold
|
tomwalters@0
|
162 new_val=current_val-current_threshold;
|
tomwalters@0
|
163 current_threshold=current_val;
|
tomwalters@0
|
164 strobe_points(counter)=ii;
|
tomwalters@0
|
165 counter=counter+1;
|
tomwalters@0
|
166 last_strobe=ii;
|
tomwalters@0
|
167 last_threshold_value=current_threshold;
|
tomwalters@0
|
168 else
|
tomwalters@0
|
169 new_val=0;
|
tomwalters@0
|
170 end
|
tomwalters@0
|
171 tresval(ii)=current_threshold;
|
tomwalters@0
|
172
|
tomwalters@0
|
173 time_since_last_strobe=(ii-last_strobe)/sr;
|
tomwalters@0
|
174 if time_since_last_strobe > w % linear falling threshold
|
tomwalters@0
|
175 const_decay=last_threshold_value/sr/strobe_decay_time;
|
tomwalters@0
|
176 current_threshold=current_threshold-const_decay;
|
tomwalters@0
|
177 current_threshold=max(0,current_threshold);
|
tomwalters@0
|
178 else % parabel for the first time y=a(x+b)^2+c
|
tomwalters@0
|
179 a=4*(1-h)/(w*w);
|
tomwalters@0
|
180 b=-w/2;
|
tomwalters@0
|
181 c=h;
|
tomwalters@0
|
182 factor=a*(time_since_last_strobe + b) ^2+c;
|
tomwalters@0
|
183 current_threshold=last_threshold_value*factor;
|
tomwalters@0
|
184 end
|
tomwalters@0
|
185
|
tomwalters@0
|
186 current_threshold=max(0,current_threshold); % cant be smaller then 0
|
tomwalters@0
|
187
|
tomwalters@0
|
188 newvals(ii)=new_val;
|
tomwalters@0
|
189 end
|
tomwalters@0
|
190
|
tomwalters@0
|
191 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
tomwalters@0
|
192 function [strobe_points,tresval,newvals]=doadaptivethresholdpeak(sigvals,options)
|
tomwalters@0
|
193 % finds every single local maximum
|
tomwalters@0
|
194 sr=options.sr;
|
tomwalters@0
|
195 tresval=zeros(size(sigvals));
|
tomwalters@0
|
196 newvals=zeros(size(sigvals));
|
tomwalters@0
|
197 strobe_points=zeros(100,1);
|
tomwalters@0
|
198 sig=signal(sigvals);
|
tomwalters@0
|
199 sig=setsr(sig,sr);
|
tomwalters@0
|
200 [t,h]=getlocalmaxima(sig);
|
tomwalters@0
|
201 strobe_points=t*sr;
|
tomwalters@0
|
202
|
tomwalters@0
|
203
|
tomwalters@0
|
204 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
tomwalters@0
|
205 function [strobe_points,tresval,newvals]=doadaptivethresholdpeakshadowplus(sigvals,options)
|
tomwalters@0
|
206 % finds every single peak and starts from that an falling threshold
|
tomwalters@0
|
207
|
tomwalters@0
|
208 current_threshold=options.current_threshold;
|
tomwalters@0
|
209 sr=options.sr;
|
tomwalters@0
|
210 tresval=zeros(size(sigvals));
|
tomwalters@0
|
211 newvals=zeros(size(sigvals));
|
tomwalters@0
|
212 nr=length(sigvals);
|
tomwalters@0
|
213 strobe_points=zeros(100,1);
|
tomwalters@0
|
214 strobe_decay_time=options.strobe_decay_time;
|
tomwalters@0
|
215 last_strobe=-inf;
|
tomwalters@0
|
216 counter=1;
|
tomwalters@0
|
217 last_threshold_value=0;
|
tomwalters@0
|
218
|
tomwalters@0
|
219 for ii=2:nr-1
|
tomwalters@0
|
220 current_val=sigvals(ii);
|
tomwalters@0
|
221 current_time=ii/sr;
|
tomwalters@0
|
222
|
tomwalters@0
|
223 if current_val>current_threshold
|
tomwalters@0
|
224 if sigvals(ii) > sigvals(ii-1) && sigvals(ii) > sigvals(ii+1)
|
tomwalters@0
|
225 new_val=current_val-current_threshold;
|
tomwalters@0
|
226 current_threshold=current_val;
|
tomwalters@0
|
227 strobe_points(counter)=ii;
|
tomwalters@0
|
228 counter=counter+1;
|
tomwalters@0
|
229 last_strobe=ii;
|
tomwalters@0
|
230 last_threshold_value=current_threshold;
|
tomwalters@0
|
231 end
|
tomwalters@0
|
232 end
|
tomwalters@0
|
233 const_decay=last_threshold_value/sr/strobe_decay_time;
|
tomwalters@0
|
234 current_threshold=current_threshold-const_decay;
|
tomwalters@0
|
235 current_threshold=max(0,current_threshold);
|
tomwalters@0
|
236 tresval(ii)=current_threshold;
|
tomwalters@0
|
237 end
|
tomwalters@0
|
238
|
tomwalters@0
|
239
|
tomwalters@0
|
240 function [strobe_points,tresval,newvals]=doadaptivethresholdadaptive(sigvals,options)
|
tomwalters@0
|
241 % the threshold is calculated relativ to the hight of the last strobe
|
tomwalters@0
|
242 % the sample rate is needed for the calculation of the next thresholds
|
tomwalters@0
|
243 % for time_constant_alpha this is a linear decreasing function that goes
|
tomwalters@0
|
244 % from the maximum value to 0 in the time_constant
|
tomwalters@0
|
245
|
tomwalters@0
|
246 current_threshold=options.current_threshold;
|
tomwalters@0
|
247 sr=options.sr;
|
tomwalters@0
|
248 last_strobe=-inf;
|
tomwalters@0
|
249
|
tomwalters@0
|
250 tresval=zeros(size(sigvals));
|
tomwalters@0
|
251 newvals=zeros(size(sigvals));
|
tomwalters@0
|
252 nr=length(sigvals);
|
tomwalters@0
|
253 strobe_points=zeros(100,1);
|
tomwalters@0
|
254
|
tomwalters@0
|
255 %when the last strobe occured
|
tomwalters@0
|
256 % last_strobe=-inf;
|
tomwalters@0
|
257 last_threshold_value=0;
|
tomwalters@0
|
258
|
tomwalters@0
|
259 % copy options to save time
|
tomwalters@0
|
260 strobe_decay_time=options.strobe_decay_time;
|
tomwalters@0
|
261
|
tomwalters@0
|
262 bunt=0.5;
|
tomwalters@0
|
263
|
tomwalters@0
|
264 % decay_time=options.strobe_decay_time;
|
tomwalters@0
|
265 % threshold_decay_constant=power(0.5,1./(options.strobe_decay_time*sr));
|
tomwalters@0
|
266
|
tomwalters@0
|
267 slope_coefficient=options.slope_coefficient;
|
tomwalters@0
|
268 slope_coefficient=0.0005;
|
tomwalters@0
|
269 minoffset=0.2;
|
tomwalters@0
|
270
|
tomwalters@0
|
271 threshold_decay_offset=-1/(options.strobe_decay_time*sr);
|
tomwalters@0
|
272 default_threshold_decay_offset=threshold_decay_offset;
|
tomwalters@0
|
273
|
tomwalters@0
|
274 counter=1;
|
tomwalters@0
|
275 for ii=1:nr
|
tomwalters@0
|
276 current_val=sigvals(ii);
|
tomwalters@0
|
277 current_time=ii/sr;
|
tomwalters@0
|
278
|
tomwalters@0
|
279 if current_val>current_threshold
|
tomwalters@0
|
280 new_val=current_val-current_threshold;
|
tomwalters@0
|
281 current_threshold=current_val;
|
tomwalters@0
|
282 strobe_points(counter)=ii;
|
tomwalters@0
|
283 counter=counter+1;
|
tomwalters@0
|
284 time_offset=ii-last_strobe; % time since last strobe
|
tomwalters@0
|
285 last_strobe=ii;
|
tomwalters@0
|
286
|
tomwalters@0
|
287 amplitude_offset=current_threshold-last_threshold_value;
|
tomwalters@0
|
288
|
tomwalters@0
|
289 last_threshold_value=current_threshold;
|
tomwalters@0
|
290
|
tomwalters@0
|
291 current_bunt=0;
|
tomwalters@0
|
292 % if amplitude_offset>0
|
tomwalters@0
|
293 % current_bunt=amplitude_offset/1;
|
tomwalters@0
|
294 % else
|
tomwalters@0
|
295 % current_bunt=0;
|
tomwalters@0
|
296 % end
|
tomwalters@0
|
297 current_threshold=current_threshold+current_bunt+minoffset;
|
tomwalters@0
|
298
|
tomwalters@0
|
299 offset=amplitude_offset/time_offset*slope_coefficient;
|
tomwalters@0
|
300
|
tomwalters@0
|
301 threshold_decay_offset=threshold_decay_offset+offset;
|
tomwalters@0
|
302 % threshold_decay_constant=power(0.5,1./(decay_time*sr));
|
tomwalters@0
|
303 else
|
tomwalters@0
|
304 new_val=0;
|
tomwalters@0
|
305 end
|
tomwalters@0
|
306 tresval(ii)=current_threshold;
|
tomwalters@0
|
307 time_since_last_strobe=(ii-last_strobe)/sr;
|
tomwalters@0
|
308
|
tomwalters@0
|
309
|
tomwalters@0
|
310 % current_threshold=current_threshold*threshold_decay_constant;
|
tomwalters@0
|
311 current_threshold=current_threshold+threshold_decay_offset;
|
tomwalters@0
|
312 current_threshold=max(current_threshold,0);
|
tomwalters@0
|
313
|
tomwalters@0
|
314 if time_since_last_strobe>0.035
|
tomwalters@0
|
315 current_threshold=0;
|
tomwalters@0
|
316 threshold_decay_offset=default_threshold_decay_offset;
|
tomwalters@0
|
317 end
|
tomwalters@0
|
318
|
tomwalters@0
|
319 newvals(ii)=new_val;
|
tomwalters@0
|
320 end
|
tomwalters@0
|
321
|
tomwalters@0
|
322
|
tomwalters@0
|
323 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
tomwalters@0
|
324 %%%% BUNT
|
tomwalters@0
|
325 function [strobe_points,tresval,newvals]=doadaptivethresholdbunt(sigvals,options)
|
tomwalters@0
|
326 % the bunt is relative to the last peak hight
|
tomwalters@0
|
327
|
tomwalters@0
|
328 current_threshold=options.current_threshold;
|
tomwalters@0
|
329 sr=options.sr;
|
tomwalters@0
|
330 last_strobe=-inf;
|
tomwalters@0
|
331
|
tomwalters@0
|
332 tresval=zeros(size(sigvals));
|
tomwalters@0
|
333 newvals=zeros(size(sigvals));
|
tomwalters@0
|
334 nr=length(sigvals);
|
tomwalters@0
|
335 strobe_points=zeros(100,1);
|
tomwalters@0
|
336
|
tomwalters@0
|
337 %when the last strobe occured
|
tomwalters@0
|
338 last_strobe_time=-inf;
|
tomwalters@0
|
339 last_threshold_value=0;
|
tomwalters@0
|
340
|
tomwalters@0
|
341 % copy options to save time
|
tomwalters@0
|
342 strobe_decay_time=options.strobe_decay_time;
|
tomwalters@0
|
343
|
tomwalters@0
|
344 % wait that many cycles to confirm a strobe
|
tomwalters@0
|
345 wait_time=options.wait_cycles/options.cf;
|
tomwalters@0
|
346
|
tomwalters@0
|
347 % if waited for too long, then strobe anyhow after some passed candidates:
|
tomwalters@0
|
348 wait_candidate_max=options.nr_strobe_candidates;
|
tomwalters@0
|
349 current_jumped_candidates=1;
|
tomwalters@0
|
350
|
tomwalters@0
|
351
|
tomwalters@0
|
352 bunt=options.bunt;
|
tomwalters@0
|
353
|
tomwalters@0
|
354 strobe_decay_time=options.strobe_decay_time;
|
tomwalters@0
|
355 last_val=sigvals(1);
|
tomwalters@0
|
356
|
tomwalters@0
|
357 counter=1;
|
tomwalters@0
|
358 for ii=2:nr-1
|
tomwalters@0
|
359 current_val=sigvals(ii);
|
tomwalters@0
|
360 next_val=sigvals(ii+1);
|
tomwalters@0
|
361 current_time=ii/sr;
|
tomwalters@0
|
362 if current_val>=current_threshold % above threshold -> criterium for strobe
|
tomwalters@0
|
363 current_threshold=current_val;
|
tomwalters@0
|
364 if current_val > last_val && current_val > next_val % only strobe on local maxima
|
tomwalters@0
|
365
|
tomwalters@0
|
366 % so far its a candidate, but is it a real strobe?
|
tomwalters@0
|
367 % look if there was a strobe in the past, that is deleted
|
tomwalters@0
|
368 if (counter>1 && current_time-strobe_time(counter-1)<wait_time) %&& current_threshold >last_threshold_value
|
tomwalters@0
|
369 % if its too long in the past, fire anyway
|
tomwalters@0
|
370 if current_jumped_candidates > wait_candidate_max
|
tomwalters@0
|
371 current_jumped_candidates=1; % reset counter
|
tomwalters@0
|
372 else
|
tomwalters@0
|
373 current_jumped_candidates=current_jumped_candidates+1;
|
tomwalters@0
|
374 counter=counter-1; % delete the last one
|
tomwalters@0
|
375 end
|
tomwalters@0
|
376 else
|
tomwalters@0
|
377 current_jumped_candidates=current_jumped_candidates+1;
|
tomwalters@0
|
378 end
|
tomwalters@0
|
379
|
tomwalters@0
|
380
|
tomwalters@0
|
381 % take this one as a new one
|
tomwalters@0
|
382 strobe_points(counter)=ii;
|
tomwalters@0
|
383 strobe_time(counter)=ii/sr;
|
tomwalters@0
|
384 counter=counter+1; % strobecounter
|
tomwalters@0
|
385 current_threshold=current_threshold*options.bunt; %increase threshold
|
tomwalters@0
|
386
|
tomwalters@0
|
387 last_strobe_time=ii/sr; % anyhow, its a candidate
|
tomwalters@0
|
388 last_threshold_value=current_threshold;
|
tomwalters@0
|
389
|
tomwalters@0
|
390 end
|
tomwalters@0
|
391 end
|
tomwalters@0
|
392 tresval(ii)=current_threshold;
|
tomwalters@0
|
393
|
tomwalters@0
|
394 const_decay=last_threshold_value/sr/strobe_decay_time;
|
tomwalters@0
|
395 current_threshold=current_threshold-const_decay;
|
tomwalters@0
|
396
|
tomwalters@0
|
397 current_threshold=max(current_threshold,0);
|
tomwalters@0
|
398 last_val=current_val;
|
tomwalters@0
|
399 end
|
tomwalters@0
|
400
|