2 SuperCollider real time audio synthesis system
3 Copyright (c) 2002 James McCartney. All rights reserved.
4 http://www.audiosynth.com
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21 //BeatTrack2 UGen implemented by Nick Collins (http://www.informatics.sussex.ac.uk/users/nc81/)
26 //need to add bestgroove option to store groove, else remove output which is currently always straight 16ths
29 static const int g_numtempi
= 120;
30 static float g_periods
[g_numtempi
]= { 1, 0.98360655737705, 0.96774193548387, 0.95238095238095, 0.9375, 0.92307692307692, 0.90909090909091, 0.8955223880597, 0.88235294117647, 0.8695652173913, 0.85714285714286, 0.84507042253521, 0.83333333333333, 0.82191780821918, 0.81081081081081, 0.8, 0.78947368421053, 0.77922077922078, 0.76923076923077, 0.75949367088608, 0.75, 0.74074074074074, 0.73170731707317, 0.72289156626506, 0.71428571428571, 0.70588235294118, 0.69767441860465, 0.68965517241379, 0.68181818181818, 0.67415730337079, 0.66666666666667, 0.65934065934066, 0.65217391304348, 0.64516129032258, 0.63829787234043, 0.63157894736842, 0.625, 0.61855670103093, 0.61224489795918, 0.60606060606061, 0.6, 0.59405940594059, 0.58823529411765, 0.58252427184466, 0.57692307692308, 0.57142857142857, 0.56603773584906, 0.5607476635514, 0.55555555555556, 0.55045871559633, 0.54545454545455, 0.54054054054054, 0.53571428571429, 0.53097345132743, 0.52631578947368, 0.52173913043478, 0.51724137931034, 0.51282051282051, 0.50847457627119, 0.50420168067227, 0.5, 0.49586776859504, 0.49180327868852, 0.48780487804878, 0.48387096774194, 0.48, 0.47619047619048, 0.47244094488189, 0.46875, 0.46511627906977, 0.46153846153846, 0.45801526717557, 0.45454545454545, 0.45112781954887, 0.44776119402985, 0.44444444444444, 0.44117647058824, 0.43795620437956, 0.43478260869565, 0.43165467625899, 0.42857142857143, 0.42553191489362, 0.42253521126761, 0.41958041958042, 0.41666666666667, 0.41379310344828, 0.41095890410959, 0.40816326530612, 0.40540540540541, 0.40268456375839, 0.4, 0.39735099337748, 0.39473684210526, 0.3921568627451, 0.38961038961039, 0.38709677419355, 0.38461538461538, 0.38216560509554, 0.37974683544304, 0.37735849056604, 0.375, 0.37267080745342, 0.37037037037037, 0.3680981595092, 0.36585365853659, 0.36363636363636, 0.36144578313253, 0.35928143712575, 0.35714285714286, 0.35502958579882, 0.35294117647059, 0.35087719298246, 0.34883720930233, 0.34682080924855, 0.3448275862069, 0.34285714285714, 0.34090909090909, 0.33898305084746, 0.33707865168539, 0.33519553072626 };
31 //float g_tempoweight[g_numtempi]= { 0.8, 0.82581988897472, 0.83651483716701, 0.84472135955, 0.85163977794943, 0.85773502691896, 0.86324555320337, 0.8683130051064, 0.87302967433402, 0.87745966692415, 0.88164965809277, 0.88563488385777, 0.88944271909999, 0.89309493362513, 0.89660917830793, 0.9, 0.90327955589886, 0.90645812948448, 0.90954451150103, 0.91254628677423, 0.91547005383793, 0.91832159566199, 0.9211060141639, 0.92382783747338, 0.92649110640674, 0.92909944487358, 0.93165611772088, 0.93416407864999, 0.93662601021279, 0.93904435743076, 0.94142135623731, 0.94375905768565, 0.94605934866804, 0.94832396974191, 0.95055453054182, 0.95275252316519, 0.9549193338483, 0.95705625319186, 0.95916448515084, 0.96124515496597, 0.96329931618555, 0.96532795690183, 0.96733200530682, 0.969312334656, 0.97126976771554, 0.97320508075689, 0.97511900715418, 0.97701224063136, 0.97888543819998, 0.98073922282301, 0.98257418583506, 0.98439088914586, 0.98618986725025, 0.98797162906496, 0.9897366596101, 0.99148542155127, 0.99321835661586, 0.99493588689618, 0.99663841605004, 0.99832633040858, 1, 0.99832633040858, 0.99663841605004, 0.99493588689618, 0.99321835661586, 0.99148542155127, 0.9897366596101, 0.98797162906496, 0.98618986725025, 0.98439088914586, 0.98257418583506, 0.98073922282301, 0.97888543819998, 0.97701224063136, 0.97511900715418, 0.97320508075689, 0.97126976771554, 0.969312334656, 0.96733200530682, 0.96532795690183, 0.96329931618555, 0.96124515496597, 0.95916448515084, 0.95705625319186, 0.9549193338483, 0.95275252316519, 0.95055453054182, 0.94832396974191, 0.94605934866804, 0.94375905768565, 0.94142135623731, 0.93904435743076, 0.93662601021279, 0.93416407864999, 0.93165611772088, 0.92909944487358, 0.92649110640674, 0.92382783747338, 0.9211060141639, 0.91832159566199, 0.91547005383793, 0.91254628677423, 0.90954451150103, 0.90645812948448, 0.90327955589886, 0.9, 0.89660917830793, 0.89309493362513, 0.88944271909999, 0.88563488385777, 0.88164965809277, 0.87745966692415, 0.87302967433402, 0.8683130051064, 0.86324555320337, 0.85773502691896, 0.85163977794943, 0.84472135955, 0.83651483716701, 0.82581988897472 };
32 //const float g_groove = 0.32;
35 static float g_sep
[8]= {0.0, 0.25, 0.5, 0.75, 0.0, 0.32, 0.5, 0.82};
36 //weight for particular step
37 static float g_weight
[4]= {1.0,0.5,0.9,0.6};
38 //weight for blurring feature envelope locally
39 static float g_weight2
[9]= {0.05, 0.1, 0.3,0.7,1.0,0.7,0.3, 0.1, 0.05};
41 //void BeatTrack2_dofft(BeatTrack2 *unit, uint32);
42 static void calculatetemplate(BeatTrack2
*unit
, int which
, int j
);
43 static void finaldecision(BeatTrack2
*unit
);
45 void BeatTrack2_Ctor(BeatTrack2
* unit
)
47 //unit->m_srate = unit->mWorld->mFullRate.mSampleRate;
48 float kblocklength
= unit
->mWorld
->mFullRate
.mBufDuration
; //seconds per control block
49 unit
->m_krlength
= kblocklength
;
50 //N features per block over numphases*2 variants for one of 120 tempi, so need at least 120 blocks to complete
52 unit
->m_phaseaccuracy
= ZIN0(3); //0.02; //20 msec resolution; could be argument of UGen
54 unit
->m_numphases
= (int*)RTAlloc(unit
->mWorld
, g_numtempi
* sizeof(int));
55 //unit->m_phases = (float**)RTAlloc(unit->mWorld, g_numtempi * sizeof(float*));
57 for (int j
=0; j
<g_numtempi
; ++j
) {
59 float period
= g_periods
[j
];
61 int num
= (int)(period
/unit
->m_phaseaccuracy
); //maximum will be 1.0/0.02 = 50
63 unit
->m_numphases
[j
]=num
;
66 // unit->m_phases[j]= (float*)RTAlloc(unit->mWorld, unit->m_numphases[j] * sizeof(float));
70 // for (i=0; i<num; ++i) {
71 // unit->m_phases[j][i] = phase;
72 // phase += unit->m_phaseaccuracy;
77 unit
->m_numfeatures
= (int)(ZIN0(1)+0.001);
82 unit
->m_scores
= (float*)RTAlloc(unit
->mWorld
, (2*unit
->m_numfeatures
) * sizeof(float));
84 unit
->m_temporalwindowsize
= ZIN0(2); //typically small, 2 seconds for fast reactions compared to 6 secs for BeatTrack
86 unit
->m_fullwindowsize
= unit
->m_temporalwindowsize
+ 1.0 + 0.1; //plus one to cover all phases of the 60bpm based period, and a further 0.1 for indexing safety; ie looking at areas around the point you're interested in
88 unit
->m_buffersize
= (int)(unit
->m_fullwindowsize
/unit
->m_krlength
); //in control blocks
91 //printf("loading test blocklength %f numfeatures %d temporal %f full %f blocks %d \n",unit->m_krlength, unit->m_numfeatures, unit->m_temporalwindowsize, unit->m_fullwindowsize, unit->m_buffersize);
95 //float ** m_pastfeatures; //for each feature, a trail of last m_workingmemorysize values
96 unit
->m_pastfeatures
= (float**)RTAlloc(unit
->mWorld
, unit
->m_numfeatures
* sizeof(float*));
98 for (int j
=0; j
<unit
->m_numfeatures
; ++j
) {
100 unit
->m_pastfeatures
[j
]= (float*)RTAlloc(unit
->mWorld
, unit
->m_buffersize
* sizeof(float));
102 Clear(unit
->m_buffersize
, unit
->m_pastfeatures
[j
]); //set all to zero at first
104 //for (i=0; i<unit->m_buffersize; ++i) {
105 // unit->m_pastfeatures[j][i] = 0.0;
113 //could avoid allocation by having a hard limit on
114 unit
->bestscore
= (float*)RTAlloc(unit
->mWorld
, 4 * unit
->m_numfeatures
* sizeof(float));
115 unit
->bestphase
= (int*)RTAlloc(unit
->mWorld
, 4 * unit
->m_numfeatures
* sizeof(int));
116 unit
->besttempo
= (int*)RTAlloc(unit
->mWorld
, 4 * unit
->m_numfeatures
* sizeof(int));
117 unit
->bestgroove
= (int*)RTAlloc(unit
->mWorld
, 4 * unit
->m_numfeatures
* sizeof(int));
119 for (int i
=0; i
<4; ++i
) {
121 int basepos
= i
*unit
->m_numfeatures
;
123 for (int j
=0; j
<unit
->m_numfeatures
; ++j
) {
124 unit
->bestscore
[basepos
+j
]= -9999.0;
125 unit
->bestphase
[basepos
+j
]= 0;
126 unit
->besttempo
[basepos
+j
]= 60;
127 unit
->bestgroove
[basepos
+j
]= 0;
135 unit
->m_phaseperblock
= unit
->m_krlength
/unit
->m_period
;
137 unit
->m_predictphase
= 0.4f
;
138 unit
->m_predictperiod
= 0.3f
;
141 unit
->m_outputphase
= unit
->m_phase
;
142 unit
->m_outputtempo
= unit
->m_currtempo
;
143 unit
->m_outputgroove
= unit
->m_groove
;
144 unit
->m_outputphaseperblock
= unit
->m_phaseperblock
;
148 unit
->m_calculationperiod
= 0.5; //every half second; could also be additional argument to UGen
149 unit
->m_calculationschedule
= 0.0;
151 //printf("srate %f conversion factor %f frame period %f \n", unit->m_srate, unit->m_srateconversion, unit->m_frameperiod);
154 int bufnum
= (int)(ZIN0(5)+0.001f
);
155 if (bufnum
>= unit
->mWorld
->mNumSndBufs
)
159 unit
->m_weightingscheme
= bufnum
<2 ? 0 : 1;
161 SndBuf
*buf
= unit
->mWorld
->mSndBufs
+ bufnum
;
162 unit
->m_tempoweights
= buf
;
163 unit
->m_weightingscheme
=2;
166 //printf("bufnum %d weightingscheme %d check %f %f\n", bufnum, unit->m_weightingscheme, unit->m_tempoweights[0], unit->m_tempoweights[119]);
174 unit
->mCalcFunc
= (UnitCalcFunc
)&BeatTrack2_next
;
179 void BeatTrack2_Dtor(BeatTrack2
*unit
)
181 RTFree(unit
->mWorld
, unit
->m_numphases
);
183 RTFree(unit
->mWorld
, unit
->m_scores
);
185 RTFree(unit
->mWorld
, unit
->bestscore
);
186 RTFree(unit
->mWorld
, unit
->bestphase
);
187 RTFree(unit
->mWorld
, unit
->besttempo
);
189 for (int j
=0; j
<unit
->m_numfeatures
; ++j
)
190 RTFree(unit
->mWorld
, unit
->m_pastfeatures
[j
]);
192 RTFree(unit
->mWorld
, unit
->m_pastfeatures
);
197 //over phases and for each groove
198 void calculatetemplate(BeatTrack2
*unit
, int which
, int j
)
202 int startcounter
= unit
->m_startcounter
;
204 int numphases
= unit
->m_numphases
[which
];
206 float period
= g_periods
[which
];
208 float blockconvert
= unit
->m_krlength
;
210 float windowsize
= unit
->m_temporalwindowsize
;
212 int buffersize
= unit
->m_buffersize
; //unit->m_fullwindowsize/unit->m_krlength; //in control blocks
214 float ** pastfeatures
= unit
->m_pastfeatures
;
215 //unit->m_pastfeatures = (float**)RTAlloc(unit->mWorld, unit->m_numfeatures * sizeof(float*));
217 int beatsfit
= (int)(windowsize
/period
); //complete beats only, or also fit as many as possible?
219 float weight
; //compensation for number of events matched; may alter equation later
221 switch (unit
->m_weightingscheme
)
224 weight
= 1.0f
; //flat
227 weight
= 1.0f
/(beatsfit
*4); //compensate for number of time points tested
230 SndBuf
* buf
= unit
->m_tempoweights
;
232 weight
= buf
->data
[which
]; //user defined temmpo biases (usually a mask on allowed tempi)
238 int numfeatures
= unit
->m_numfeatures
;
240 float * scores
= unit
->m_scores
; //[2*numfeatures];
242 float * bestscore
= unit
->bestscore
;
243 int * bestphase
= unit
->bestphase
;
244 int * besttempo
= unit
->besttempo
;
245 int * bestgroove
= unit
->bestgroove
;
247 for (int i
=0; i
<numphases
; ++i
) {
250 //for (j=0; j<2; ++j)
251 for (int k
=0; k
<numfeatures
; ++k
)
254 float phaseadd
= i
*unit
->m_phaseaccuracy
;
256 //calculation for a particular phase of template
257 //for (j=0; j<2; ++j) {
259 for(int h
=0; h
<beatsfit
; ++h
) {
261 for(int l
=0; l
<4; ++l
) {
263 float sep
= phaseadd
+ (h
*period
)+ ((g_sep
[j
*4+l
]) * period
);
264 float weight
= g_weight
[l
];
266 int blocks
= (int)((sep
/blockconvert
)+0.5); //round to nearest
268 //convert sep to control periods and find appropriate point in source data
269 int index
= (startcounter
+ buffersize
- blocks
)%(buffersize
);
272 //widen over four either side
273 for (int m
= (-4); m
<5;++m
) {
275 int actualindex
= (index
+buffersize
+m
)%(buffersize
);
277 for (int k
=0; k
<numfeatures
; ++k
) {
279 int scoreindexnow
= 2*k
+j
;
281 //could widen this value here, even based on cubic interpolation etc
282 scores
[scoreindexnow
] += weight
* (g_weight2
[m
+4])* (pastfeatures
[k
][actualindex
]);
286 //scores[2*k+j] += weight * (pastfeatures[k][index]);
296 //update any winners from scores
297 //for (j=0; j<2; ++j) {
299 for (int k
=0; k
<numfeatures
; ++k
) {
301 float scorenow
= (scores
[2*k
+j
]) * weight
;
303 //NEED TO STORE J IF PRESERVING SENSE OF GROOVE
305 if(scorenow
>bestscore
[k
]) {
307 tmpindex
= numfeatures
+k
;
308 //shift up to make room
309 bestscore
[tmpindex
]= bestscore
[k
]; bestphase
[tmpindex
]= bestphase
[k
];
310 besttempo
[tmpindex
]= besttempo
[k
]; bestgroove
[tmpindex
]=bestgroove
[k
];
312 bestscore
[k
]= scorenow
; bestphase
[k
]= i
; besttempo
[k
]= which
; bestgroove
[k
]=j
;
314 //printf("bestscore %f bestphase %d besttempo %d bestgroove %d \n", bestscore[k],bestphase[k],besttempo[k], bestgroove[k]);
316 else if (scorenow
>bestscore
[numfeatures
+k
]) {
318 tmpindex
= numfeatures
+k
;
319 bestscore
[tmpindex
]= scorenow
; bestphase
[tmpindex
]= i
;
320 besttempo
[tmpindex
]= which
; bestgroove
[tmpindex
]=j
;
333 //a winner must appear at least twice, across features, and be superior to the secondbest in those features too by some margins
334 //a consistency check could also run to look at change from last time to this
335 void finaldecision(BeatTrack2
*unit
)
338 int bestcandidate
=0;
339 int bestpreviousmatchsum
=0; //(-1); //should be 0, but allowing different for now
340 float excess
; //, consistency;
341 //int exactmatches, closematches; //can be out by a few indices on period; could match on tempo but not phase etc
342 //combine these four factors in one master score?
344 for (int i
=0; i
<unit
->m_numfeatures
; ++i
) {
348 float secondbest
= unit
->bestscore
[unit
->m_numfeatures
+i
];
349 excess
= (secondbest
!=0) ? (unit
->bestscore
[i
]/ secondbest
): unit
->bestscore
[i
];
350 int tempo
= unit
->besttempo
[i
];
352 //could check consistency too by looking at phase update from last prediction in same feature
354 for (int j
=0; j
<unit
->m_numfeatures
; ++j
) {
358 if (abs(unit
->besttempo
[j
]-tempo
)<5) matchsum
++;
362 //check over all previous features
363 if (abs(unit
->besttempo
[2*unit
->m_numfeatures
+j
]- tempo
)<5) matchsum
++;
367 //printf("i %d matchsum %d excess %f \n",i, matchsum, excess);
369 if(secondbest
!= 0) matchsum
+= (int)excess
;
371 //so must have at least one match //&& (excess>1.03)
372 if ((matchsum
>bestpreviousmatchsum
)) {bestcandidate
= i
; bestpreviousmatchsum
= matchsum
; foundgood
=1;}
377 //consistency: could require it to win twice; have a candidatepending which makes a phase prediction; only let through if prediction fulfilled
379 //unit->m_amortlength will be numtempi *2 = 240
381 float bestphase
= fmod( ((unit
->bestphase
[bestcandidate
] * unit
->m_phaseaccuracy
) + (unit
->m_krlength
* (unit
->m_amortlength
)))/(unit
->m_period
), (float)1.0);
383 //if(unit->m_prediction) {
385 if ((fabs(bestphase
- unit
->m_predictphase
)< ((2*(unit
->m_phaseaccuracy
))/unit
->m_predictperiod
)) && (fabs( (g_periods
[unit
->besttempo
[bestcandidate
]]) - unit
->m_predictperiod
) <0.04) ) {
387 unit
->m_period
= unit
->m_predictperiod
;
388 //time elapsed since a known beat is phase of winner in seconds, to calculation start point, plus time for calculation (120 control blocks) divided by period, modulo 1.0
389 unit
->m_phase
= bestphase
;
390 unit
->m_currtempo
= 1.f
/unit
->m_period
;
391 unit
->m_phaseperblock
= unit
->m_krlength
/unit
->m_period
;
397 //unit->m_prediction=false;
403 unit
->m_predictperiod
= g_periods
[unit
->besttempo
[bestcandidate
]];
405 //time elapsed since a known beat is phase of winner in seconds, to calculation start point, plus time for calculation (120 control blocks) divided by period, modulo 1.0
406 unit
->m_predictphase
= fmod( ( (unit
->bestphase
[bestcandidate
] * unit
->m_phaseaccuracy
) + (unit
->m_krlength
* (unit
->m_amortlength
)) + unit
->m_calculationperiod
)/(unit
->m_period
),(float)1.0);
413 //unit->m_period = g_periods[unit->besttempo[bestcandidate]];
414 ////time elapsed since a known beat is phase of winner in seconds, to calculation start point, plus time for calculation (120 control blocks) divided by period, modulo 1.0
415 //unit->m_phase= fmod( ((unit->bestphase[bestcandidate] * unit->m_phaseaccuracy) + (unit->m_krlength * 120))/(unit->m_period), 1.0);
417 //unit->m_currtempo = 1.0/unit->m_period;
418 //unit->m_phaseperblock = unit->m_krlength/unit->m_period;
426 void BeatTrack2_next(BeatTrack2
*unit
, int wrongNumSamples
)
428 //keep updating feature memories
429 unit
->m_counter
= (unit
->m_counter
+1)%(unit
->m_buffersize
);
431 int busnum
= (int)(ZIN0(0)+0.001f
);
433 //unit->m_features = unit->mWorld->mControlBus + busnum;
435 float * features
= unit
->mWorld
->mControlBus
+ busnum
;
437 //hmm, is this pointer guaranteed to stay the same? may have to update each time...
438 for (int j
=0; j
<unit
->m_numfeatures
; ++j
) {
439 unit
->m_pastfeatures
[j
][unit
->m_counter
]= features
[j
]; //unit->m_features[j];
442 unit
->m_calculationschedule
+= unit
->m_krlength
;
444 //check for new calculation round
445 if(unit
->m_calculationschedule
> unit
->m_calculationperiod
) {
447 unit
->m_calculationschedule
-= unit
->m_calculationperiod
;
449 //reset best scores and move old to previous slots
450 for (int i
=0; i
<2; ++i
) {
452 int pos1
= (2+i
)*unit
->m_numfeatures
;
453 int pos2
= i
*unit
->m_numfeatures
;
455 for (int j
=0; j
<unit
->m_numfeatures
; ++j
) {
456 unit
->bestscore
[pos1
+j
]= unit
->bestscore
[pos2
+j
];
457 unit
->bestscore
[pos2
+j
]= -9999.0;
458 unit
->bestphase
[pos1
+j
]= unit
->bestphase
[pos2
+j
];
459 unit
->bestphase
[pos2
+j
]= 0;
460 unit
->besttempo
[pos1
+j
]= unit
->besttempo
[pos2
+j
];
461 unit
->besttempo
[pos2
+j
]= 60;
466 //state 0 is do nothing
467 unit
->m_amortisationstate
=1;
468 unit
->m_amortcount
=0;
469 unit
->m_amortlength
=g_numtempi
*2; //
470 //unit->m_amortisationsteps=0;
472 //store essential data
473 unit
->m_startcounter
= unit
->m_counter
;
475 unit
->m_currphase
=unit
->m_phase
;
479 //keeps incrementing but will be reset with each calculation run
480 //unit->m_amortisationsteps=unit->m_amortisationsteps+1;
482 //if state nonzero do something
483 switch(unit
->m_amortisationstate
) {
485 break; //do nothing case
486 case 1: //calculate acf
487 calculatetemplate(unit
,unit
->m_amortcount
>> 1, unit
->m_amortcount
%2);
489 unit
->m_amortcount
=unit
->m_amortcount
+1;
491 if(unit
->m_amortcount
==unit
->m_amortlength
) {
492 unit
->m_amortisationstate
=2;
493 //unit->m_amortlength=1;
494 //unit->m_amortcount=0;
497 case 2: //done calculating template matches, now decide whether to follow through
499 unit
->m_amortisationstate
=0;
509 //test if impulse to output
510 unit
->m_phase
+=unit
->m_phaseperblock
;
512 //if(unit->m_counter%400==0) printf("phase %f period %f\n", unit->m_phase, unit->m_period);
514 //if not locked, update output phase from model phase, else keep a separate output phase
517 //printf("lock %f \n",lock);
521 unit
->m_outputphase
= unit
->m_phase
;
522 unit
->m_outputtempo
= unit
->m_currtempo
;
523 unit
->m_outputgroove
= unit
->m_groove
;
524 unit
->m_outputphaseperblock
= unit
->m_phaseperblock
;
527 unit
->m_outputphase
+=unit
->m_outputphaseperblock
;
531 if (unit
->m_phase
>= 1.f
) {unit
->m_phase
-= 1.f
;}
533 //0 is beat, 1 is quaver, 2 is semiquaver, 3 is actual current tempo in bps
534 //so no audio accuracy with beats, just asap, may as well be control rate
538 ZOUT0(3)=unit
->m_outputtempo
; //*0.016666667;
539 ZOUT0(4)=unit
->m_outputphase
;
540 ZOUT0(5)=unit
->m_outputgroove
;
543 if (unit
->m_outputphase
>= 1.f
) {
547 unit
->m_outputphase
-= 1.f
;
556 if (unit
->m_outputphase
>=0.5 && unit
->halftrig
==0) {
562 float groove
= unit
->m_outputgroove
*0.07;
564 if (unit
->m_outputphase
>=(0.25+groove
) && unit
->q1trig
==0) {
569 if (unit
->m_outputphase
>=(0.75+groove
) && unit
->q2trig
==0) {