aboutsummaryrefslogtreecommitdiff
path: root/beat.c
blob: a2f565eff22f3c046202959671b935843663f22e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
/* --------------------------- beat  ------------------------------------------ */
/*                                                                              */
/* Detect the beats per minute of a MIDI stream.                                */
/* Written by Olaf Matthes (olaf.matthes@gmx.de)                                */
/* Based on code written by Robert Rowe.                                        */
/* Get source at http://www.akustische-kunst.org/puredata/maxlib/               */
/*                                                                              */
/* This program is free software; you can redistribute it and/or                */
/* modify it under the terms of the GNU General Public License                  */
/* as published by the Free Software Foundation; either version 2               */
/* of the License, or (at your option) any later version.                       */
/*                                                                              */
/* This program is distributed in the hope that it will be useful,              */
/* but WITHOUT ANY WARRANTY; without even the implied warranty of               */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                */
/* GNU General Public License for more details.                                 */
/*                                                                              */
/* You should have received a copy of the GNU General Public License            */
/* along with this program; if not, write to the Free Software                  */
/* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.  */
/*                                                                              */
/* Based on PureData by Miller Puckette and others.                             */
/*                                                                              */
/* ---------------------------------------------------------------------------- */

#include "m_pd.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define BEAT_LONG 1500			/* longest time we take into concideration (40 bpm) */
#define BEAT_SHORT 300			/* shortest time we take into concideration (200 bpm) */

static char *version = "beat v0.1, written by Olaf Matthes <olaf.matthes@gmx.de>";

typedef struct
{
	t_int	points;					/* number of points assigned to this theory */
	double  expect;					/* time of next expected hit */
	t_int	onbeat;					/* whether (1) or not (0) it was on the beat */
} beat_theory;

typedef struct						/* used for sorting theories */
{
	t_int points;
	t_int theory;
} beat_sort_record;

 
typedef struct beat
{
  t_object x_ob;
  t_clock  *x_clock;
  t_outlet *x_outbpm;               /* beat as MIDI note number */
  t_outlet *x_outms;                /* beat in milliseconds */
  t_outlet *x_outbeat;				/* send a bang whenever beat is 'on beat' */
  t_int    x_print;                 /* switch printing to console window on / off */

  t_int    x_num_beats;				/* number of beats we've received */
  double   x_beat_period;			/* time in ms until next expected beat / beat pulse */
  beat_theory	x_beats[BEAT_LONG];
  double   x_beatexpect;			/* expected time for next beat */
  t_int    x_on_beat;				/* indicate if last event was on beat */
  t_int    x_band_percent;

  t_int    x_pitch;
  t_int    x_velo;
	/* helpers needed to do the time calculations */
  double   x_this_input;
  double   x_last_input;
  double   x_lasttime;
  double   x_lastlasttime;
} t_beat;

/* ---------------- mathematical functions to work with doubles  -------------- */
static double double_abs(double value)
{
	if(value < 0)
		return (value * -1);
	else
		return (value);
}

/* --------------- beat  stuff ------------------------------------------------ */
	/* evaluate results: find theory that is the most likely one and
       print out internal data to console window if print is enabled */
static int beat_evaluate(t_beat *x)
{
	int i, j, K;
	char string[256];
	char info[40];
	beat_sort_record theories[BEAT_LONG], *sortp, R;
	int value;	/* the result of the sorting */

	for (i = 0; i < BEAT_LONG; i++)
	{		/* prepare sort records */
		sortp         = &(theories[i]);
		sortp->points = x->x_beats[i].points;
		sortp->theory = i;
	}
	for (j = 2; j < BEAT_LONG; j++)
	{		/* sort */
		i = j - 1;
		K = theories[j].points;
		R = theories[j];
		while (i > 0)
		{
			if (K >= theories[i].points)
			{
				theories[i+1] = R;
				break;
			}
			else
			{
				theories[i+1] = theories[i];
				i -= 1;
			}
		}
		if (i==0) theories[i+1] = R;
	}
		/* get leading result */
	sortp = &(theories[BEAT_LONG - 1]);
	value = sortp->theory;	/* get our resulting theory */

	if(x->x_print)
	{
		post("         0         1         2         3         4         R            E");
		*string = '\0';						/* print out five leading theories */
		sprintf(info, "%4g", x->x_this_input);
		strcat(string, info);
		for(i = 1; i < 6; i++)
		{
			sortp = &(theories[BEAT_LONG - i]);
			sprintf(info, " %4d[%3d]", (int) sortp->theory, (int) sortp->points);
			strcat(string, info);
		}
		sprintf(info, "  %g %g", clock_getlogicaltime(), x->x_beatexpect);
		strcat(string, info);
		post(string);
	}

	return value;
}

	/* reduce duration to fit into our processing window */
	/* some sort of 'double modulo'...                   */
static double beat_reduce_offset(double duration)
{
	double temp	= duration;
	int divisor = 2;					/* first try dividing by two */
	while (temp > BEAT_LONG)		    /* while duration is too long */
		temp = duration / divisor++;    /* divide by progressively higher divisors */
	return temp;				        /* return a value in bounds */
}

/*
 * beat_eligible: determine whether an event is eligible for consideration
 * as a beat theory
 */
static int beat_eligible(double candidate, int* offsets, int num_offsets)
{
	double diff;
	int i;

	if (candidate >= BEAT_LONG)		/* if too long try subharmonics */
		candidate = beat_reduce_offset(candidate);
	
		/* if candidate is close to one already found */
	for(i = 0; i < num_offsets; i++)
	{
		diff = double_abs((candidate - offsets[i]));
		if (diff < offsets[i]/20) {
			if (candidate > offsets[i])
				++offsets[i]; else			/* pull existing one */
			if (candidate < offsets[i])		/* toward new candidate */
				--offsets[i];
			return 0;						/* declare candidate ineligible */
		}
	}
	return candidate;						/* otherwise return legal candidate */
}

static void beat_float(t_beat *x, t_floatarg f)
{
	t_int velo = x->x_velo;
	int i, j, indx;
	int num_offsets, candidate;
	int low_limit, high_limit, width, deviation;
	int points, band, center_offset, period;
	beat_theory* t;
	int offsets[7];
	static int factors[10] = 
		{ 200, 50, 300, 150, 66, 400, 600, 133, 33, 75 };
	double now = clock_getlogicaltime();
	t_float outvalue;

	x->x_pitch = (t_int)f;
	x->x_this_input = clock_gettimesince(x->x_last_input);

	if(velo != 0)	/* note-on received */
	{
		if(++x->x_num_beats == 1)
		{
			goto time;					/* only one event, no beats yet */
		}

		num_offsets = 0;
		candidate  = beat_eligible(x->x_this_input, offsets, num_offsets);
		if(candidate)
			offsets[num_offsets++] = candidate;				/* offset[0] set to incoming offset */
 
		if(x->x_num_beats > 2)
		{								/* if three events */
				/* check previous for eligibility */
			candidate = beat_eligible(x->x_lasttime, offsets, num_offsets);
			if (candidate)
				offsets[num_offsets++] = candidate;
			candidate  = x->x_this_input + x->x_lasttime;	/* add current and previous offsets */
			candidate  = beat_eligible(candidate, offsets, num_offsets);
			if (candidate)									/* add to list if eligible */
				offsets[num_offsets++] = candidate;
		}

		if(x->x_num_beats > 3)
		{
			candidate  = beat_eligible(x->x_lastlasttime, offsets, num_offsets);
			if (candidate)
				offsets[num_offsets++] = candidate;
			candidate += x->x_lasttime;
			candidate  = beat_eligible(candidate, offsets, num_offsets);
			if (candidate)
				offsets[num_offsets++] = candidate;
		}

		indx = 0;
		for(i = num_offsets; i < 7; i++)
		{
			offsets[i] = 0;
			if (indx >= 10) break;
			candidate  = 0;
			while ((indx < 10) && (!candidate))
				candidate = beat_eligible((x->x_this_input * factors[indx++])/100, offsets, num_offsets);
			if (candidate)
				offsets[num_offsets++] = candidate;
		}
		
		for(i = 0; i < num_offsets; i++)
		{
			band = offsets[i] * x->x_band_percent / 100;
			if ((low_limit = offsets[i] - band) < 0)		/* add points in a critical band */
				low_limit  = 0;								/* around calculated offset */
			if ((high_limit = offsets[i] + band) > BEAT_LONG)
				high_limit = BEAT_LONG;
			center_offset = offsets[i];						/* midpoint of increment */
			points = 0;
			for (j = low_limit; j < high_limit; j++)
			{
				if ((points = x->x_beats[j].points) > 0)
				{		/* if there is already activation */
					deviation = j - center_offset;			/* record deviation from midpoint */
					x->x_beats[j].points = 0;
					if (deviation < 0) {					/* if there is activation below midpoint */
						t = &(x->x_beats[j+1]);				/* take theory one above prior */
					} else
					if (deviation > 0) {					/* if there is activation above midpoint */
						t = &(x->x_beats[j-1]);				/* take theory one below prior */
					} else
						t = &(x->x_beats[j]);				/* landed right on it */
					t->points = points + (num_offsets-i);
					break;
				}
			}
			if (!points)
				x->x_beats[center_offset].points = num_offsets - i;
		}

			/* boost hits, and suppress theories with missed beats */
		period = 0;
		points = 0;
		for (i = BEAT_SHORT; i < BEAT_LONG; i++)
		{
			t = &(x->x_beats[i]);
			width = 5 > (t->expect / 7) ? 5 : (t->expect / 7);
			t->expect -= x->x_this_input;
			t->onbeat  = 0;
			if(double_abs(t->expect) <= width)	/* lies within range */
			{
				t->expect = i;
				t->onbeat = 1;
	 			if (t->points > 0)
					t->points += 4;				/* add 4 points */
			}
			else if(t->expect < 0)
			{
				t->points -= 8;
				t->expect  = i;
			}
			if (t->points < 0)   t->points =   0; else
			if (t->points > 200) t->points = 200;
			if (t->points > points)
			{
				points = t->points;
				period = i;
			}
		}



		x->x_beat_period    = (double)period;
		t					= &(x->x_beats[period]);
		x->x_beatexpect		= now + (double)t->expect;
		x->x_on_beat	    = t->onbeat;
		
time:
		x->x_lastlasttime = x->x_lasttime;
		x->x_lasttime = x->x_this_input; //now;
		x->x_last_input = now;

		if(x->x_on_beat)outlet_bang(x->x_outbeat);
		outvalue = (t_float)beat_evaluate(x);
		outlet_float(x->x_outms, outvalue);
		if(x->x_beat_period)outlet_float(x->x_outbpm, (t_float)(60000.0 / outvalue));
	}
	return;
}

static void beat_ft1(t_beat *x, t_floatarg f)
{
	x->x_velo = (t_int)f;
}

	/* toggle printing on/off */
static void beat_print(t_beat *x)
{
	if(x->x_print)x->x_print = 0;
	else x->x_print = 1;
}

static void beat_reset(t_beat *x)
{
	int i;

	for(i = 0; i < BEAT_LONG; i++)
	{
		x->x_beats[i].points = 0;
		x->x_beats[i].expect = i;
		x->x_beats[i].onbeat = 0;
	}
	x->x_lastlasttime = 0;
	x->x_lasttime = 0;
	x->x_num_beats = 0;
	x->x_beat_period = 0;
	x->x_on_beat = 0;
}

static t_class *beat_class;

static void beat_free(t_beat *x)
{
	/* nothing to do */
}

static void *beat_new(t_floatarg f)
{
    t_beat *x = (t_beat *)pd_new(beat_class);
    inlet_new(&x->x_ob, &x->x_ob.ob_pd, gensym("float"), gensym("ft1"));
	x->x_outbpm = outlet_new(&x->x_ob, gensym("float"));
	x->x_outms = outlet_new(&x->x_ob, gensym("float"));
	x->x_outbeat = outlet_new(&x->x_ob, gensym("bang"));

	beat_reset(x);
	x->x_band_percent = 4;	/* allow 4% 'jitter' by default */
	if(f)x->x_band_percent = (t_int)f;

	post("beat: band percentage set to %d", x->x_band_percent);

    return (void *)x;
}

#ifndef MAXLIB
void beat_setup(void)
{
    beat_class = class_new(gensym("beat"), (t_newmethod)beat_new,
    	(t_method)beat_free, sizeof(t_beat), 0, A_DEFFLOAT, 0);
#else
void maxlib_beat_setup(void)
{
    beat_class = class_new(gensym("maxlib_beat"), (t_newmethod)beat_new,
    	(t_method)beat_free, sizeof(t_beat), 0, A_DEFFLOAT, 0);
#endif
    class_addfloat(beat_class, beat_float);
	class_addmethod(beat_class, (t_method)beat_ft1, gensym("ft1"), A_FLOAT, 0);
	class_addmethod(beat_class, (t_method)beat_reset, gensym("reset"), 0);
	class_addmethod(beat_class, (t_method)beat_print, gensym("print"), 0);
#ifndef MAXLIB
    
    post(version);
#else
	class_addcreator((t_newmethod)beat_new, gensym("beat"), A_DEFFLOAT, 0);
    class_sethelpsymbol(beat_class, gensym("maxlib/beat-help.pd"));
#endif
}