-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathhand_scheduling.scd
161 lines (142 loc) · 4.79 KB
/
hand_scheduling.scd
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
~pattern.play;
(
~quit = false;
~cond = Condition.new(false);
~cleanup = EventStreamCleanup.new;
~pattern = Pbind(
\scale, Scale.chromatic,
\octave, 4,
\root, 0, // key of C
\amp, 1.0,
\degree, Pseq([-1, 0, \rest, 0, -1, 0, \rest, 0, -1, 0, \rest, 0, 1, 0, -1 ], inf),
\dur, Pseq([0.25, 0.5, 0.25, 1.0, 0.25, 0.5, 0.25, 1.0, 0.25, 0.5, 0.25, 0.5, 1.0, 0.5, 1.0], inf)
);
~stream = ~pattern.asStream;
~period = 1.0;
~range = 0.5;
~min_values_for_mean = 7;
~max_values_for_mean = 15;
~tap_time_deltas = List.new;
~last_tap = nil;
MIDIIn.connectAll;
~midi_port = MIDIIn.findPort("Logidy UMI3", "Logidy UMI3 MIDI 1");
if (~midi_port.notNil, {
~midi_func = MIDIFunc.noteOn({ | value, number, channel, source_id |
var tap_time = Process.monotonicClockTime;
~cond.test = true;
~cond.signal;
if (~last_tap.notNil, {
var unlikely_delta;
var delta = tap_time - ~last_tap;
// If we've already established a mean period and this value
// is much greater we consider that input has stopped and is now
// running again, we discard the delta and start timing anew.
if (~period.notNil, {
unlikely_delta = ~period * 2.0;
}, {
unlikely_delta = delta * 2.0;
});
if (delta < unlikely_delta, {
~tap_time_deltas.addFirst(delta);
while ({ ~tap_time_deltas.size > ~max_values_for_mean }, {
~tap_time_deltas.pop();
});
});
});
~last_tap = tap_time;
if (~tap_time_deltas.size >= ~min_values_for_mean, {
var sum = 0.0;
~tap_time_deltas.do({ | x | sum = sum + x; });
~period = sum / ~tap_time_deltas.size.asFloat;
sum = 0.0;
~tap_time_deltas.do({ | x | sum = (~period - x).squared + sum; });
~range = sum / (~tap_time_deltas.size.asFloat - 1.0);
});
});
}, {
"unable to find MIDI controller.".postln;
});
// For every tap pulse train we compute mean, std_dev period between taps based on weighted
// average of last k taps. When a tap comes in we measure the timing and compare it against
// the expected next tap time and std_dev. There is some range of expected timing of next tap
// which is r*std_dev, with r some parameter to scale standard_deviation of tap values.
// Tap timing could be any of the following:
//
// Super early, e.g. well before expected - could consider a debouncing case when std_dev is tight,
// and disregard as spurious.
//
// Early, but within std_dev - fire next beat of scheduling now, recompute mean timing, then start
// waiting now for next tap.
//
// ** At this point tap would be exactly on time, so we would fire events based on timer going off,
// but will need to ensure that behavior here is sane
//
// Late, but within std_dev - we've already fired event, but we re-compute mean tempo based on longer
// period tap and then restart the clock for when we should wake from this time
//
// Super late, outside of std_dev but somehow differentiated from super early for the next beat.
// might be worth considering these cases same as super early for next beat? But how does one
// slow down the tap tempo-er?
fork {
// For any event scheduled for longer then one beat we decrement the timer
// one beat at a time.
var current_event = nil, remaining_time = 0.0;
var next_beat_expected_at = nil;
var delta = 0.0;
var beat_number = 0;
current_event = ~stream.next(());
current_event.dur = current_event.dur * ~period;
while ({~quit.not}, {
// If the current event has a remaining_time > 1
// we take no action this iteration. Otherwise we
// can assume next event takes place within this
// beat.
while ({ remaining_time < ~period }, {
if (current_event.isRest.not, {
if (remaining_time == 0.0, {
current_event.play();
}, {
var e = current_event;
SystemClock.sched(remaining_time, { e.play(); });
});
});
remaining_time = remaining_time + current_event.dur;
current_event = ~stream.next(());
current_event.dur = current_event.dur * ~period;
});
remaining_time = remaining_time - 1.0;
next_beat_expected_at = Process.monotonicClockTime + ~period;
~cond.test = false;
fork {
~period.wait;
~cond.test = true;
~cond.signal;
};
~cond.wait;
~cond.test = false;
delta = next_beat_expected_at - Process.monotonicClockTime;
if (delta > 0, {
// Taps earlier than the expected range will require some
// additional waiting, as they could be either spurious or
// possibly late taps from last beat.
if (delta > ~range, {
// Too early to fire another beat, wait for already fired
// timeout.
~cond.wait;
// Super early taps, within range of the previous beat, are
// likely late taps. We add a little extra wait before firing
// next beat.
if (delta > (~period - ~range), {
(~period - delta).wait;
});
});
});
});
};
)
(
~midi_func.free;
~quit = true;
~cond.test = true;
~cond.signal;
)