forked from Stazed/seq42
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathnotes_testing.txt
1114 lines (775 loc) · 69.8 KB
/
notes_testing.txt
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
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
Following are notes and results of testing.
They are NOT in chronological order.
On 06/10/2016 06:41 AM, Chris Ahlstrom wrote:
Can you give a summary of what you fixed in JACK support and what
you think is still wrong in both sequencer64 and seq32?
From email reply 06/11/2016
jack,jack,jack....
Well, first let me explain how I think it works. From what I can tell, there is only one difference between jack master and jack slave. The master gets it's jack_timebase_callback() called and gets to send to jack BBT (and other) info. Any client, master or slave can send position changes and start/stop messages. It appears that the BBT is largely ignored by all other time-line based DAWs or sequencers (that I checked - non-timeline, qtractor).
Seq24, and as imported into sequencer64 are the exceptions... and it does not work.
From my 6/01 commit:
If another jack client is supplying tempo/BBT info that is different from seq42 (as Master),
the perfroll grid will be incorrect. Perfroll uses internal temp/BBT and cannot update on
the fly. Even if seq42 could support tempo/BBT changes, all info would have to be available
before the transport start, to work. For this reason, the tempo/BBT info will be plugged from
the seq42 internal settings here... always. This is the method used by probably all other jack
clients with some sort of time-line. The jack API indicates that BBT is optional and AFIK,
other sequencers only use frame & frame_rate from jack for internal calculations. The tempo
and BBT info is always internal. Also, if there is no Master set, then we would need to plug
it here to follow the jack frame anyways.
You can test the last sentence on sequencer64. Start qjackctl and look at the BBT display before any other client is attached. It will show -.-.--- . No master is set. Now start sequencer64 and set as slave. The qjackctl display should still show blanks. Press start in sequencer64 or qjackctl and the transport will start rolling but sequencer64 will freeze. This is the same behavior that occurred in the original seq24. It is because seq24 is trying to use BBT queried from jack in the output_func() that does not exist. This behavior was imported into sequencer64 and I have seen a number of complaints over the years in forums about the seq24 inconsistent behavior. This is probably why.
You can also try this same test with other programs such as non-timeline, qtractor are the ones I checked. Since these (and probably all) of the other time-line based programs set themselves as master, you have to replace them with another master such as sequencer64, last one attached wins. Then disconnect sequencer64 from jack. Check qjackctl for the blanks to be sure that you have no master set. Now start the transport. The other programs follow, obviously using their own internal tempo/BBT using jack frame and frame_rate only from jack. You can hook up several different programs in this way, remove the master, and if they all have the same internal tempo/BBT set, they will run perfectly in sync. You can also change transport positions with these programs when rolling, as well as from qjackctl and they will still stay in sync.
What this all means to me is that as far as time-line type programs are concerned, BBT from jack is just pretty, but superficial display in qjackctl. It's not necessary for them to sync when received from jack. Other type programs, the only one I can think of is klick, do use BBT.
Try this test with klick. Set sequencer64 up as master, and klick running. Start the transport rolling then change on the perfroll the beats per bar. klick won't adjust. Sequencer64 is not resetting the beats per bar when changed internally? Is the button callback connected? Try this same test with seq32 and klick adjusts to the new beats per bar. klick will not work when no master is set.
For the jack_timebase_callback():
All I did was strip out the calculations regarding new position changes and always use internal tempo/BBT. It seems to me that as far as tempo/BBT is concerned, the only thing that matters is jack frame and frame_rate. Just use internal tempo/BBT on all calculations. Your calculations in timebase callback seem to work (might be overkill), except for using pos->frame_rate. If the client sending a position change uses jack_transport_locate(), then they are only sending jack frame position and no BBT.
The perhaps overkill...
Your conditional:
if (new_pos || ! (pos->valid & JackPositionBBT))
and seq24 original's version:
if ( state_last == JackTransportStarting && state_current == JackTransportRolling )
Why is this needed? Obviously seq24's callback did not work regardless of the check, but why bother??
You get a new jack frame or the transport sends one, and calculate BBT to plug into pos->.
And by the time jack slow sync catches up and changes to JackTransportRolling you are a number of cycles past new_pos anyway. I suspect this check may be necessary for something like klick or perhaps sooperlooper?(I have not used) that are not time-line based? klick adjusts to changes in beats per measure (probably tempo also). Maybe sooperlooper as well?
I just took this stuff out and it seems to work with everything I tested including klick.
Onward...
position_jack() jack_transport_reposition() jack_transport_locate()...
This is were I was concerned about seq32. See my latest commits from last night, I mean morning 4:11 am.
Let me expain :)
The changes I made in seq32 are just my personal preferences.
The original seq24 did this in position_jack()
if ( m_jack_running ){
jack_transport_locate( m_jack_client, 0 );
}
return;
Obviously just started at 0 frame position. It was only called when seq24 started the transport rolling from various different locations that I remember and was a really ugly complicated mess that took me many hours to decipher. I finally concluded that everything should be called from a single perform start_playing(). I think you did something similar. nightmares!
Anyway regarding position_jack(). As you know, there was the code after the above return that was never used. My guess is that the seq24 team decided to follow Rob Buse's original plan of a minimalist sequencer for LIVE performance only. Jack reposition's hesitate because jack waits for slow sync clients to catch up, thus making repositions useless in a live setup. They just abandoned any running position changes and if synced to jack, just start at 0.
It is this issue that I deviated from the original seq24. I used seq24 as a sort of creative experimental looping tool, for editing. Not for live purposes... yet?? Because it is more of an editor for me, I wanted seq32 master to change position, loop in sync with other programs. If you just want sequencer64 for live purposes, then jack_position() is not needed. But, I suspect since you added the second performance editor, and the event editor, that you want better editing capabilities.
So, to explain what I did for seq32 as of last night...
The set_left_frame() stuff worked but really, really, really ugly.
The actual transport reposition calculations/BBT in position_jack() that were NOT used were actually very close. The problem with them was... I can't think of the correct programming term... the variable size was too small... the calculations were truncated?? They also hard coded beats_per_bar and beat_type to 4.
The below calculation is where the problem was. I replaced this with set_left_frame() which calculated m_left_frame.
Before:
pos.frame = (jack_nframes_t) ( (current_tick * rate * 60.0)
/ (pos.ticks_per_beat * pos.beats_per_minute) );
Now:
uint64_t tick_rate = ((uint64_t)m_jack_frame_rate * current_tick * 60.0);
long tpb_bpm = ticks_per_beat * beats_per_minute;
uint64_t jack_frame = tick_rate / tpb_bpm;
The only real difference between the before calculation above and the one that calculated m_left_frame in set_left_frame() was I separated things and added a few more casts to uint64_t and stored them in uint64_t's to stop the truncation. It was really ugly looking... but I think it calculated correctly... well at least I did not find it to be wrong... :)
I removed set_left_frame() and m_left_frame last morning.
I moved the set_left_frame() calc back into position_jack() and commented out all the BBT stuff, and replaced jack_transport_reposition() with jack_transport_locate(). The difference is that jack_transport_locate() just sends the frame position, NOT BBT. And it works...
From my note:
This jack_frame calculation is all that is needed to change jack position
The BBT calc below can be sent but will be overridden by the first call to
jack_timebase_callback() of any master set. If no master is set, then the
BBT will display the new position but will not change even if the transport
is rolling. I'm going with the KISS (keep it simple stupid) rule here.
There is no need to send BBT on position change - the fact that the function
jack_transport_locate() exists and only uses the frame position is proof that
BBT is not needed!
The reason that I decided to dump BBT on position change altogether is the part about sending BBT when there is no master set. If you do this with klick connected and hit the precise position change it will cause the "click" to play, endlessly loudly... also, the unchanging BBT displayed in qjackctl could be confusing to users.
You don't want to send BBT position info if there is no master, and it gets immediately overridden by any master's timebase callback. Seems useless to me. I left the BBT code (uncommented) in in case I am wrong.
Previous position_jack() used set_left_frame() which was hard coded to use m_left_tick. The last commit of last morning I added a new feature that I previously added in seq42.
FYI almost everything done to seq32 is cut and paste from seq42. I use the two programs interchangeably and want the features to be the same.
This new feature required the tick be passed into position_jack(). m_left_tick is the left tick marker in the perfroll as you know. Currently, this is also how I get jack to loop.. call position_jack() which repositions to the left tick in output_func when m_looping or when called from start_playing(). Instead of only positioning to m_left_tick, it now accepts a tick variable and then can adjust to any position. The new feature is one "filched" from non-timeline which allows the user to adjust the position on the perfedit to the mouse pointer with key-p. The mouse must be focused on the perfroll.
In seq42 I also added FF and rewind buttons that use it also.
If you don't want the key-p type position change, then m_left_tick can be hard code.
To summarize how things work now in seq32:
The "nightmare" that was replaced with perform::start_playing(), and the added buttons of "song mode" on the mainwnd and jack sync (xpm) in the perfedit were done for consistency. The original seq24....... could drive a person to... learn programming!
I was going to try to explain all the variations the program used to start rolling, based on which place you pressed the space bar or whether you used the buttons or key-binding, in mainwnd, seqroll, perfedit. And all the rules changed if connected to jack, depending on the song/live button hidden in the file/options/jack sync menu.
Instead I will just explain how seq32 works.
The "song mode" button was moved from the jack sync menu and now applies always, not just for jack.
When starting from the mainwnd, seqroll(keybind), or when connected to jack and started by the transport, when the song button is pressed it will cause the perfedit to play. It will start at the left tick marker if in jack master mode, and also when not connected to jack. When started from the perfedit, the song button is ignored and ALWAYS plays the song.
When the song button is unchecked, it will play live if started from the mainwnd and seqroll jack or not.
The jack sync button does what it did before.. connects and disconnects jack. I moved it to perfedit for personal preference and to be able to attach key-binding. I also attached key-binding to the song button. This allows me to turn both on and off from the seqroll without having to go all the way to the file/options/jack sync menu or to the perfedit for song play.
The difference between jack master and slave:
Master starts at left tick marker and will loop.
Slave just starts at transport position... like sequencer64 master.
When in live mode, master starts at 0. Slave continues at transport position.
The new key-p feature will work all cases, master, slave and when not connected to jack. I stopped it from working if m_usemidiclock... not sure how that works yet.
How you want sequencer64 to work with jack as master vs slave or even if you want to have position changes is matter of preference. This is mine.
Well thats my long winded thoughts on jack. Hope it helps.
06/13/2016 email
decided to put back BBT calcs in position_jack()... it's easier to debug!
also fixed a bug, it didn't work with bw.
re-factored to call a common function for both position_jack() and jack_timebase_callback().
--------------------
Update - revert back to using jack_transport_locate() on position change (no BBT sent) - commit 6/17/2016. Because upon further research, is seems to be the norm.
-------------------
Ardour: http://manual.ardour.org/synchronization/timecode-generators-and-slaves/
JACK Transport
When slaved to jack, Ardour's transport will be identical to JACK-transport. As opposed to other slaves, Ardour can be used to control the JACK transport states (stopped/rolling). No port connections need to be made for jack-transport to work.
JACK-transport does not support vari-speed, nor offsets. Ardour does not chase the timecode but is always in perfect sample-sync with it.
JACK-transport also includes temp-based-time information in Bar:Beats:Ticks and beats-per-minute. However, only one JACK application can provide this information at a given time. The checkbox Session > Properties > JACK Time Master configures Ardour to act as translator from timecode to BBT information.
----------------------
transport.c -- JACK transport master example client.
static void timebase(jack_transport_state_t state, jack_nframes_t nframes,
jack_position_t *pos, int new_pos, void *arg)
{
double min; /* minutes since frame 0 */
long abs_tick; /* ticks since frame 0 */
long abs_beat; /* beats since frame 0 */
if (new_pos || time_reset) {
pos->valid = JackPositionBBT;
pos->beats_per_bar = time_beats_per_bar;
pos->beat_type = time_beat_type;
pos->ticks_per_beat = time_ticks_per_beat;
pos->beats_per_minute = time_beats_per_minute;
time_reset = 0; /* time change complete */
/* Compute BBT info from frame number. This is relatively
* simple here, but would become complex if we supported tempo
* or time signature changes at specific locations in the
* transport timeline. */
min = pos->frame / ((double) pos->frame_rate * 60.0);
abs_tick = min * pos->beats_per_minute * pos->ticks_per_beat;
abs_beat = abs_tick / pos->ticks_per_beat;
pos->bar = abs_beat / pos->beats_per_bar;
pos->beat = abs_beat - (pos->bar * pos->beats_per_bar) + 1;
pos->tick = abs_tick - (abs_beat * pos->ticks_per_beat);
pos->bar_start_tick = pos->bar * pos->beats_per_bar *
pos->ticks_per_beat;
pos->bar++; /* adjust start to bar 1 */
#if 0
/* some debug code... */
fprintf(stderr, "\nnew position: %" PRIu32 "\tBBT: %3"
PRIi32 "|%" PRIi32 "|%04" PRIi32 "\n",
pos->frame, pos->bar, pos->beat, pos->tick);
#endif
} else {
/* Compute BBT info based on previous period. */
pos->tick +=
nframes * pos->ticks_per_beat * pos->beats_per_minute
/ (pos->frame_rate * 60);
while (pos->tick >= pos->ticks_per_beat) {
pos->tick -= pos->ticks_per_beat;
if (++pos->beat > pos->beats_per_bar) {
pos->beat = 1;
++pos->bar;
pos->bar_start_tick +=
pos->beats_per_bar
* pos->ticks_per_beat;
}
}
}
}
---------------------------------
non sequencer:
/** callback for when we're Timebase Master, mostly taken from
* transport.c in Jack's example clients. */
/* FIXME: there is a subtle interaction here between the tempo and
* JACK's buffer size. Inflating ticks_per_beat (as jack_transport
* does) diminishes the effect of this correlation, but does not
* eliminate it... This is caused by the accumulation of a precision
* error, and all timebase master routines I've examined appear to
* suffer from this same tempo distortion (and all use the magic
* number of 1920 ticks_per_beat in an attempt to reduce the magnitude
* of the error. Currently, we keep this behaviour. */
void
Transport::timebase ( jack_transport_state_t, jack_nframes_t nframes, jack_position_t *pos, int new_pos, void * )
{
if ( new_pos || ! _done )
{
pos->valid = JackPositionBBT;
pos->beats_per_bar = transport._master_beats_per_bar;
pos->ticks_per_beat = 1920.0; /* magic number means what? */
pos->beat_type = transport._master_beat_type;
pos->beats_per_minute = transport._master_beats_per_minute;
double wallclock = (double)pos->frame / (pos->frame_rate * 60);
unsigned long abs_tick = wallclock * pos->beats_per_minute * pos->ticks_per_beat;
unsigned long abs_beat = abs_tick / pos->ticks_per_beat;
pos->bar = abs_beat / pos->beats_per_bar;
pos->beat = abs_beat - (pos->bar * pos->beats_per_bar) + 1;
pos->tick = abs_tick - (abs_beat * pos->ticks_per_beat);
pos->bar_start_tick = pos->bar * pos->beats_per_bar * pos->ticks_per_beat;
pos->bar++;
_done = true;
}
else
{
pos->tick += nframes * pos->ticks_per_beat * pos->beats_per_minute / (pos->frame_rate * 60);
while ( pos->tick >= pos->ticks_per_beat )
{
pos->tick -= pos->ticks_per_beat;
if ( ++pos->beat > pos->beats_per_bar )
{
pos->beat = 1;
++pos->bar;
pos->bar_start_tick += pos->beats_per_bar * pos->ticks_per_beat;
}
}
}
}
--------------------------------
The above code from non-sequencer was tried in seq42. The only modification was to call jack_BBT_position() instead of the if() portion. The math is the same.
And seemed to work... until a position change was sent from a slave (non-timeline) that uses jack_transport_locate() - i.e. no BBT sent with change. Then it froze the whole machine!!! (granted it was a slow laptop)
The problem can be shown with the above terminal dumps with modified code to stop the freezing (plugged amounts from internal values).
Two tests were done: First a position change was sent from seq42. Then one sent from non-timeline as above.
The "|| ! _done" simply checks if the user changed tempo or time signature and was removed.
The "else bbb" is the "else" of the above if(new-pos) conditional.
With if(new_pos)
Position change from position_jack() -- internal seq42 key-p
jack_timebase_callback() [1] [0] [221189]
pos->bar [ 3]
else bbb [ 3: 3: 113]
jack_BBT_position bbb [14: 3: 960] jack_tick [104640.000000] < from position_jack() - [14: 3: 960] -- internal sent
jack_timebase_callback() [1] [0] [222214]
pos->bar [ 14] < notice the change from bar 3 to 14 before the new_pos!!!!!
else bbb [14: 3:1049]
jack_timebase_callback() [3] [1] [1201725] < now the new_pos hits the [1] also the [3] is state JackTransportStarting
jack_BBT_position bbb [14: 3: 960] jack_tick [104640.000000] < from timebase callback because of the new_pos - [14: 3: 960] again
jack_timebase_callback() [1] [0] [1201731]
pos->bar [ 14] < back to normal (else)
else bbb [14: 3:1049]
jack_timebase_callback() [1] [0] [1202755]
Position change from non-timeline -- external key-p
jack_timebase_callback() [1] [0] [192518]
pos->bar [ 3]
else bbb [ 3: 1:1461]
jack_timebase_callback() [1] [0] [193542]
pos->bar [135755842] < notice the bar changed to junk before new_pos was set!!!!!
else bbb [135774856: 4:1489] < junk BBT because of the timing and because non-timeline did not send BBT as slave.
jack_timebase_callback() [3] [1] [466944] < new_pos is the second item [1] - it comes after position is already changed with potential junk!!!
jack_BBT_position bbb [ 6: 2: 339] jack_tick [40659.069388] < internal because of new_pos
jack_timebase_callback() [1] [0] [466950]
pos->bar [ 6] < back to normal
else bbb [ 6: 2: 428]
jack_timebase_callback() [1] [0] [467974]
pos->bar [ 6]
The obvious reason is that new_pos comes after the actual position change gets set. And this is what the jack API says:
"This function is called immediately after process() in the same thread whenever the transport is rolling,
or when any client has requested a new position in the previous cycle."
Do I understand this correctly? "...when any client has requested a new position in the PREVIOUS cycle."
From the screen dump it is AFTER the new position is sent in the previous cycle!!!
So from what this says and does, new_pos is useless if you need to know now, not later. And using this code, if the new position is sent with NO BBT then there is junk (or zero) in all the fields, except pos->frame. The reason for the freeze was because:
while ( pos->tick >= pos->ticks_per_beat )
is NEVER resolved since pos->ticks_per_beat and pos->tick have garbage because only pos->frame is sent.
A simple test with actual non-sequencer confirms the bug. new_pos does not work if position changes are sent with jack_transport_locate(), i.e no BBT is sent.
Below is the potential solution - From sequencer64: if (new_pos || ! (pos->valid & JackPositionBBT))
The check for new_pos was removed as below. Also, all the original code in the non-sequencer callback from the else down was used without modification, i.e. no plugged values to stop the freeze.
With: if ( ! (pos->valid & JackPositionBBT) )
Position change from position_jack() -- internal seq42 key-p
pos->bar [ 2]
else bbb [ 2: 4:1779]
jack_timebase_callback() [1] [0] [173062]
pos->bar [ 2]
else bbb [ 2: 4:1868]
jack_BBT_position bbb [12: 2: 0] jack_tick [86400.000000] < [if ( ! (pos->valid & JackPositionBBT) )] hits!
jack_timebase_callback() [1] [0] [174086]
pos->bar [ 12]
else bbb [12: 2: 89]
jack_timebase_callback() [3] [1] [992250] < actual new_pos hits!
pos->bar [ 12]
Position change from non-timeline -- external key-p
pos->bar [ 3]
else bbb [ 3: 2: 698]
jack_timebase_callback() [1] [0] [204806]
jack_BBT_position bbb [ 3: 2: 553] jack_tick [17833.447619] < [if ( ! (pos->valid & JackPositionBBT) )] hits!
jack_timebase_callback() [3] [1] [589824] < actual new_pos hits!
pos->bar [ 3]
else bbb [ 3: 2: 642]
jack_timebase_callback() [1] [0] [589831]
pos->bar [ 3]
It works!!! ....???....
Upon further testing here is the screen dump when a position change is sent from a slave client that uses jack_transport_reposition() to send BBT to jack. A second instance of seq42 was used in which the tempo, beats per measure, and beat type were different than the master seq42.
Ignore the difference in dump print format, it was changed to read easier...
With new position sent from seq42 (slave) with BBT different temp/bp_measure/bw
else bbb [ 3: 3: 938]
jack_timebase: state[1]: new_pos[0]: current_frame[285704]
else bbb [10: 1: 58] < new BBT hits from slave - does not trigger [if ( ! (pos->valid & JackPositionBBT))]
jack_timebase: state[3]: new_pos[1]: current_frame[904329]
else bbb [10: 1: 116] < this new calc is from the SLAVE!!! From here forward the slave BBT is used!!!
jack_timebase: state[1]: new_pos[0]: current_frame[904337]
else bbb [10: 1: 174]
Because the pos->valid test is now true, only the else gets used, using the slaves BBT. What this means is that the BBT sent from the master now follows the slave BBT forward... endlessly different from the master out of sync. Until the master sends a new position change of it's own! Then back to the master's BBT. Also since seq42 was set to only use internal tempo/bp_measure/bw the master actually plays as set by the user (internal) but sends the BBT of the slave.
With the whole sequencer64 code:
if (new_pos || ! (pos->valid & JackPositionBBT))
else bbb [11: 4:1727]
jack_timebase: state[1]: new_pos[0]: current_frame[968640]
else bbb [ 2: 7: 66] < new BBT hits from slave calculating & sending the slave BBT to qjackctl
jack_timebase: state[3]: new_pos[1]: current_frame[646800] < new_pos hits & state = 3 = JackTransportStarting
jack_BBT_position bbb [ 8: 2: 640] jack_tick [56320.000000] < seq42 master re-calculates to master BBT
jack_timebase: state[1]: new_pos[0]: current_frame[646808]
else bbb [ 8: 2: 729] < back on the masters BBT
jack_timebase: state[1]: new_pos[0]: current_frame[647831]
else bbb [ 8: 2: 818]
So new_pos is needed to readjust to the master BBT in the odd case of a slave sending a different tempo/time sig than the master. At worst, it appears that only one cycle will be wrong during JackTransportStarting. This is actually visible on the qjackctl display sometimes - due to the slow sync delay.
But it should work... every time...maybe??
Further research:
non-sequencer allows tempo/measure/bw to be changed from another program if non is the slave. It is not really timeline based and is more of an elaborate looping sequencer, so it seems to work... if you don't hit the bug.
TODO: Compile non-sequencer with the '64 fix and see if it works. UPDATE - it works!!! -- submit bug report with proposed solution.
Also, found additional bug freeze similar to old seq24 and per sequencer64 email. When non-sequencer is slave, with no master set, it freezes when the transport starts rolling. Obviously as slave it attempts to use the master tempo/sig/BBT which does not exist - so freeze. The pos->valid & JackPositionBBT solution could also be used to test and plug internal only when this happens. Unlike seq24, non-sequencer intentionally uses the master info when in slave mode. The interaction between non-timeline and non-sequencer when timeline sends a tempo or time signature change during play is really cool!!!
TODO: try the pos->valid solution on the slave with no master freeze as well.
Other programs: Qtractor, non-timeline, ardour, Hydrogen, non-sequencer.
All of these only send position changes with jack_transport_locate() always!!!!!!!!!! No BBT is ever sent by them on reposition!!!!!!
This was confirmed in all the above cases except ardour by grep ing the clone. All found jack_transport_locate() and none found jack_transport_reposition(). Ardour was confirmed by setting the program to master then replacing it with a new master then removing the master. On position change from inside ardour, no BBT displays in qjackctl. From testing with seq42, if BBT is sent from the slave, it will show in qjackctl when there is no master set.
Also, previously researched the issue of how to determine from the client position if you have been replaced as master. Cannot find the exact results of the search but I recall that it could NOT be done. There is no way to query jack to find if the client is replaced that I could find. So this would seem to indicate ardour, could not adjust its behavior based on its status. i.e. it does the same thing when it is actual master vs. displaced master. No BBT sent.
The reason it appears that they all send BBT when they are actual master is because of their timebase callback.
Per the jack API as previously quoted regarding new_pos: "when any client has requested a new position in the previous cycle" the jack_timebase_callback() gets called. So when a new position is sent by the master, with no BBT, the timebase callback is immediately called and sends BBT to qjackctl.
This seems to confirm my original conclusion to NOT send BBT on position_jack() calls - just use jack_transport_locate().
Also checked out the timebase callbacks of non-timeline, hydrogen, qtractor below:
Qtractor:
static void qtractorAudioEngine_timebase ( jack_transport_state_t,
jack_nframes_t, jack_position_t *pPos, int, void *pvArg )
{
qtractorAudioEngine *pAudioEngine
= static_cast<qtractorAudioEngine *> (pvArg);
qtractorSession *pSession = pAudioEngine->session();
qtractorTimeScale::Cursor& cursor = pSession->timeScale()->cursor();
qtractorTimeScale::Node *pNode = cursor.seekFrame(pPos->frame);
unsigned short bars = 0;
unsigned int beats = 0;
unsigned long ticks = pNode->tickFromFrame(pPos->frame) - pNode->tick;
if (ticks >= (unsigned long) pNode->ticksPerBeat) {
beats = (unsigned int) (ticks / pNode->ticksPerBeat);
ticks -= (unsigned long) (beats * pNode->ticksPerBeat);
}
if (beats >= (unsigned int) pNode->beatsPerBar) {
bars = (unsigned short) (beats / pNode->beatsPerBar);
beats -= (unsigned int) (bars * pNode->beatsPerBar);
}
// Time frame code in bars.beats.ticks ...
pPos->valid = JackPositionBBT;
pPos->bar = pNode->bar + bars + 1;
pPos->beat = beats + 1;
pPos->tick = ticks;
// Keep current tempo (BPM)...
pPos->beats_per_bar = pNode->beatsPerBar;
pPos->ticks_per_beat = pNode->ticksPerBeat;
pPos->beats_per_minute = pNode->tempo;
pPos->beat_type = float(1 << pNode->beatDivisor);
}
----------------------------------------
Hydrogen
void JackOutput::jack_timebase_callback(jack_transport_state_t state,
jack_nframes_t nframes,
jack_position_t *pos,
int new_pos,
void *arg)
{
JackOutput *me = static_cast<JackOutput*>(arg);
if (! me) return;
Hydrogen * H = Hydrogen::get_instance();
Song* S = H->getSong();
if ( ! S ) return;
unsigned long PlayTick = ( pos->frame - me->bbt_frame_offset ) / me->m_transport.m_nTickSize;
pos->bar = H->getPosForTick ( PlayTick );
double TPB = H->getTickForHumanPosition( pos->bar );
if ( TPB < 1 ) return;
/* We'll cheat there is ticks_per_beat * 4 in bar
so every Hydrogen tick will be multipled by 4 ticks */
pos->ticks_per_beat = TPB;
pos->valid = JackPositionBBT;
pos->beats_per_bar = TPB / 48;
pos->beat_type = 4.0;
pos->beats_per_minute = H->getTimelineBpm ( pos->bar );
pos->bar++;
// Probably there will never be an offset, cause we are the master ;-)
#ifndef JACK_NO_BBT_OFFSET
pos->valid = static_cast<jack_position_bits_t> ( pos->valid | JackBBTFrameOffset );
pos->bbt_offset = 0;
#endif
if (H->getHumantimeFrames() < 1) {
pos->beat = 1;
pos->tick = 0;
pos->bar_start_tick = 0;
} else {
/* how many ticks elpased from last bar ( where bar == pattern ) */
int32_t TicksFromBar = ( PlayTick % (int32_t) pos->ticks_per_beat ) * 4;
pos->bar_start_tick = PlayTick - TicksFromBar;
pos->beat = TicksFromBar / pos->ticks_per_beat;
pos->beat++;
pos->tick = TicksFromBar % (int32_t) pos->ticks_per_beat;
#if 0
// printf ( "\e[0K\rBar %d, Beat %d, Tick %d, BPB %g, BarStartTick %g",
printf ( "Bar %d, Beat %d, Tick %d, BPB %g, BarStartTick %g\n",
pos->bar, pos->beat,pos->tick, pos->beats_per_bar, pos->bar_start_tick );
#endif
}
}
------------------------------------
Non-timeline
void
Engine::timebase ( jack_transport_state_t, jack_nframes_t, jack_position_t *pos, int )
{
position_info pi = timeline->solve_tempomap( pos->frame );
pos->valid = JackPositionBBT;
pos->beats_per_bar = pi.beats_per_bar;
pos->beat_type = pi.beat_type;
pos->beats_per_minute = pi.tempo;
pos->bar = pi.bbt.bar + 1;
pos->beat = pi.bbt.beat + 1;
pos->tick = pi.bbt.tick;
pos->ticks_per_beat = 1920.0; /* FIXME: wrong place for this */
/* FIXME: fill this in */
pos->bar_start_tick = 0;
}
None of these use new_pos!!!
None of these use the sequencer64 check to pos->valid either.
Non-timeline allows for tempo/measure/bw changes along the timeline and grep revealed no other check of JackPositionBBT.
All of the above plug internal tempo/measure/type on each cycle to insure that the slave info, if sent, does not override the master.
Example of what is sent when using jack_transport_locate() - frame only.
pos->beats_per_minute[120.000000]: pos->frame_rate[44100]
pos->beat_type[4.000000]: pos->beats_per_bar[4.000000]
pos->beats_per_minute[0.000000]: pos->frame_rate[21] < junk from position change sent by qjackctl (frame rate varies & not 0)
pos->beat_type[0.000000]: pos->beats_per_bar[0.000000] < 0's for these
pos->beats_per_minute[120.000000]: pos->frame_rate[44100]
pos->beat_type[4.000000]: pos->beats_per_bar[4.000000]
It appears that bpm, beat-typ, beats_per_bar will be 0: could substitute one of these if(pos->beats_per_minute = 0) instead of the pos->valid check....
Also did a simple math check of the BBT direct calculation in seq42/32 to the computation of BBT info based on "previous period" used by non-sequencer and sequencer64. (BTW the "previous period" calc is exactly the same as transport.c in the jack example programs).
This was simply done by printing the "previous period" BBT results, then re-calling the jack_BBT_position() immediately after to print its results. The differences were very minimal. About once every 5 or 6 cycles a rounding difference of a single digit in the tick calculation. No differences in beats and bars.
Conclusion:
The sequencer64 check for if (new_pos || ! (pos->valid & JackPositionBBT)) won't work if a slave sends BBT different from master for only one cycle. This perhaps never is done anyway...
seq42 will revert back to only sending frame with jack_transport_locate() (as everyone else does).
Do the benefits of perhaps slightly faster speed of pos->valid/new_pos and very minimal accuracy improvement of the "previous period" calculation, out way the potential remote chance of a slave sending BBT with different tempo, etc and screwing up the sync BBT display for one cycle? There is one other extremely remote chance that a slave will send the pos->valid JackPositionBBT flag and not properly fill the BBT fields sent. If this happened, a freeze like non-sequencer might occur.
The conservative approach will be kept and the jack_BBT_position() calculation will always be used. KISS. It's cleaner, easier to maintain, and involves the least risk of crash.
Don't send BBT on position change. Use jack_transport_locate().
-----------------------------------
looping
if ( m_looping && m_playback_mode )
{
static bool jack_position_once = false;
if ( current_tick >= get_right_tick() )
{
#ifdef JACK_SUPPORT
if(m_jack_running && m_jack_master && !jack_position_once)
{
position_jack(true, m_left_tick);
jack_position_once = true;
}
#endif // JACK_SUPPORT
double leftover_tick = current_tick - (get_right_tick());
#ifdef JACK_SUPPORT // to avoid xruns on FF or rewind
if(m_jack_running && m_jack_transport_state != JackTransportStarting)
play( get_right_tick() - 1 );
#endif // JACK_SUPPORT
if(!m_jack_running)
play( get_right_tick() - 1 );
reset_sequences();
set_orig_ticks( get_left_tick() );
current_tick = (double) get_left_tick() + leftover_tick;
}
else
jack_position_once = false;
}
#ifdef JACK_SUPPORT // To avoid xruns on FF or rewind
if(m_jack_running && m_jack_transport_state != JackTransportStarting)
play( (long) current_tick );
#endif // JACK_SUPPORT
if(!m_jack_running)
play( (long) current_tick );
---------------------------------
Default looping: output_func()
if ( m_looping && m_playback_mode )
{
if ( current_tick >= get_right_tick() )
{
double leftover_tick = current_tick - (get_right_tick());
if(!m_jack_running)
play( get_right_tick() - 1 );
reset_sequences();
set_orig_ticks( get_left_tick() );
current_tick = (double) get_left_tick() + leftover_tick;
}
else
}
if(!m_jack_running)
play( (long) current_tick );
This calculates the amount the current tick (play tick) is beyond the loop marker (right_tick).
This is needed because current_tick calc is not exact in terms of the position of the right_tick so it may go beyond. The leftover_tick is the calc beyond the right_tick. The - play( get_right_tick() - 1 ); is the last play before resetting to the left_tick via - set_orig_ticks( get_left_tick() ); The reset sequences is needed to shut off any notes in the middle of the right_tick marker so they will not be left on continued playing. It then turns them back on for next loop. The adjustment of adding the leftover tick to the current tick is to make the play seamless and timed since this is where the play should be if the current tick had not gone past the right tick. Immediatly after the loop, the adjusted current_tick is played.
--------------------------------
Jack looping - the following is added:
static bool jack_position_once = false;
if(m_jack_running && m_jack_master && !jack_position_once)
{
position_jack(true, m_left_tick);
jack_position_once = true;
}
if(m_jack_running && m_jack_transport_state != JackTransportStarting)
play( get_right_tick() - 1 );
jack_position_once = false;
if(m_jack_running && m_jack_transport_state != JackTransportStarting)
play( (long) current_tick );
Jack looping hesitates due to "slow sync" clients.
The entire - if ( m_looping && m_playback_mode ) routine will play several times before the call to position_jack() takes effect.
When jack loops, the first time - if ( current_tick >= get_right_tick() ) hits, then position_jack() is called. The remainder of the loop will run as before, adjusting for the amount of time past the right_tick. But the jack reposition takes 3 (seq42 while cycles) cycles before it actually gets adjusted by jack. On each seq42 cycle the current tick is adjusted by the jack frame location beyond the right-tick. The - if ( current_tick >= get_right_tick() ) will catch it each time, and adjust the play accordingly. The - static bool jack_position_once = false; was added to prevent position_jack() from being called for the second and third cycles (perhaps more?). After the jack reposition hits, then jack sets the frame to the seq42 left_tick position.... and waits....for....the....slow...sync...clients. During this wait, which varies, and according to the jack API can be as long as 2 seconds, the same frame, left tick, will be played because jack does not progress. Also, because of the seq42 adjustment for the 3 cycles beyond the right_tick, seq42 has already played three cycles past the left_tick. The jack reposition sets the play position backwards to the left_tick mark. The backward reposition and repeated play at the left_tick mark caused some notes to hang and many xruns when connected to zyn. The xruns and hangs were especially bad when using the FF or rewind buttons. Thus the need for - if(m_jack_running && m_jack_transport_state != JackTransportStarting). This stops play when jack is waiting for slow sync. And it really works! The FF/rewind no longer hesitates and produces NO xruns. This is actually better than when using FF/rewind without jack. The number of xruns without jack were very minimal - but none with jack. The >> and << keys were also tested with qjackctl and also produced no xruns. Also, during FF/rewind an occasional JackTransportRolling would hit and give the effect of an analog FF (tape) ... without the pitch change.
From within the if ( m_jack_running ) {} routine:
if ( m_looping && m_playback_mode )
{
if ( current_tick >= get_right_tick() )
{
while ( current_tick >= get_right_tick() )
{
double size = get_right_tick() - get_left_tick();
current_tick = current_tick - size;
}
off_sequences();
set_orig_ticks( (long)current_tick );
}
}
The above routine - similar to the primary looping routine is only run for jack purposes. It's purpose is to re-adjust the current tick to align with the jack frame by subtracting incremently the size of the loop until within the right loop marker. This is only called on the change from JackTransportStarting to JackTransportRolling, i.e. a position change or startup, in which the frame is beyond the right tick marker. It allows seq42 to loop independently from jack, but in sync with the frame calculated BBT. It is only used when in slave mode. In master mode it will adjust within the loop marker then send a position change upon reaching the right tick as previously indicated.
------------------------------------
Testing BW for default - non jack
The original seq24 and imported into seq42/32 did not adjust for beat width (bw) timing when NOT connected to jack.
The bug was fixed with the following simple addition in output_func() - commit 6/13/2016:
before: int bpm = m_master_bus.get_bpm();
after : int bpm = m_master_bus.get_bpm() * ( 4.0 / m_bw);
The following test was used to confirm the accuracy and consistancy with jack.
Jack uses jack_ticks_delta and the default uses delta_tick to adjust the play location.
jack_ticks_delta is double: delta_tick is a long.
play() accepts a long;
Two accumulators were set to add the delta ticks of both items. Both calcs run when in jack mode and could be run together.
ex:
delta [3473.000000]: jack_delta[3465.926531] > bw = 1
delta [1708.000000]: jack_delta[1704.594286] > bw = 2
delta [892.000000]: jack_delta[890.279184] > bw = 4
delta [1193.000000]: jack_delta[1191.484082] > bw = 8
delta [632.000000]: jack_delta[631.771429] > bw = 16
various time durations were run and there were no cumulative differences.
The difference is minimal i.e. the fix runs at the same speed.
TODO: Stop the calc for delta_tick (non jack) from running when in jack mode.
----------------------------------
The following code was tested in mainwnd timeout() to test non slow sync capabilities.
#ifdef JACK_SUPPORT
if(m_mainperf->m_jack_running && !global_is_running)
{
jack_transport_state_t state = jack_transport_query( m_mainperf->m_jack_client, nullptr );
if(state == JackTransportRolling || state == JackTransportStarting )
{
m_mainperf->m_jack_transport_state_last = JackTransportStarting;
m_mainperf->inner_start( global_song_start_mode );
//printf("JackTransportState [%d]\n",m_mainperf->m_jack_transport_state);
}
}
#endif // JACK_SUPPORT
The jack_set_sync_callback() was commented out and terminal dump of the play() function was taken during jack looping. There were no other jack clients running, not even qjackctl. The terminal dump indicated the same hesitation for slow sync clients - appx 7 seq42 cycles at the reposition location. It appears that jack always hesitates?
The API:
"The new position takes effect in two process cycles. If there are slow-sync clients and the transport is already rolling,
it will enter the JackTransportStarting state and begin invoking their sync_callbacks until ready.
This function is realtime-safe."
Must be that the "two process cycles" for jack = appx 7 seq42 cycles. This hesistation was no different than when connected as "slow sync". It appears that seq42 does not need to be slow sync... but it makes no difference since the seq42 sync_callback responds immediatly.
---------------------------------------
http://www.blitter.com/~russtopia/MIDI/~jglatt/tech/midispec.htm
-------------------------------------
EVENT_MIDI_SONG_POS
http://www.blitter.com/~russtopia/MIDI/~jglatt/tech/midispec/ssp.htm
Song Position Pointer
Category: System Common
Purpose
Some master device that controls sequence playback sends this message to force a slave device to cue the playback to a certain point in the song/sequence. In other words, this message sets the device's "Song Position". This message doesn't actually start the playback. It just sets up the device to be "ready to play" at a particular point in the song.
Status
0xF2
Data
Two data bytes follow the status. Just like with the Pitch Wheel, these two bytes are combined into a 14-bit value. (See Pitch Wheel). This 14-bit value is the MIDI Beat upon which to start the song. Songs are always assumed to start on a MIDI Beat of 0. Each MIDI Beat spans 6 MIDI Clocks. In other words, each MIDI Beat is a 16th note (since there are 24 MIDI Clocks in a quarter note).
Errata
Example: If a Song Position value of 8 is received, then a sequencer (or drum box) should cue playback to the third quarter note of the song. (8 MIDI beats * 6 MIDI clocks per MIDI beat = 48 MIDI Clocks. Since there are 24 MIDI Clocks in a quarter note, the first quarter occurs on a time of 0 MIDI Clocks, the second quarter note occurs upon the 24th MIDI Clock, and the third quarter note occurs on the 48th MIDI Clock).
Often, the slave device has its playback tempo synced to the master via MIDI Clock. See Syncing Sequence Playback.
http://www.blitter.com/~russtopia/MIDI/~jglatt/tech/midispec/wheel.htm
Two data bytes follow the status. The two bytes should be combined together to form a 14-bit value. The first data byte's bits 0 to 6 are bits 0 to 6 of the 14-bit value. The second data byte's bits 0 to 6 are really bits 7 to 13 of the 14-bit value. In other words, assuming that a C program has the first byte in the variable First and the second data byte in the variable Second, here's how to combine them into a 14-bit value (actually 16-bit since most computer CPUs deal with 16-bit, not 14-bit, integers):
unsigned short CombineBytes(unsigned char First, unsigned char Second)
{
unsigned short _14bit;
_14bit = (unsigned short)Second;
_14bit <<= 7;
_14bit |= (unsigned short)First;
return(_14bit);
}
--------------------------------------------------
http://www.blitter.com/~russtopia/MIDI/~jglatt/tech/midispec/seq.htm
Syncing Sequence Playback
A sequencer is a software program or hardware unit that "plays" a musical performance complete with appropriate rhythmic and melodic inflections (ie, plays musical notes in the context of a musical beat).
Often, it's necessary to synchronize a sequencer to some other device that is controlling a timed playback, such as a drum box playing its internal rhythm patterns, so that both play at the same instant and the same tempo. Several MIDI messages are used to cue devices to start playback at a certain point in the sequence, make sure that the devices start simultaneously, and then keep the devices in sync until they are simultaneously stopped. One device, the master, sends these messages to the other device, the slave. The slave references its playback to these messages.
The message that controls the playback rate (ie, ultimately tempo) is MIDI Clock. This is sent by the master at a rate dependent upon the master's tempo. Specifically, the master sends 24 MIDI Clocks, spaced at equal intervals, during every quarter note interval.(12 MIDI Clocks are in an eighth note, 6 MIDI Clocks in a 16th, etc). Therefore, when a slave device counts down the receipt of 24 MIDI Clock messages, it knows that one quarter note has passed. When the slave counts off another 24 MIDI Clock messages, it knows that another quarter note has passed.
For example, if a master is set at a tempo of 120 BPM (ie, there are 120 quarter notes in every minute), the master sends a MIDI clock every 20833 microseconds. (ie, There are 1,000,000 microseconds in a second. Therefore, there are 60,000,000 microseconds in a minute. At a tempo of 120 BPM, there are 120 quarter notes per minute. There are 24 MIDI clocks in each quarter note. Therefore, there should be 24 * 120 MIDI Clocks per minute. So, each MIDI Clock is sent at a rate of 60,000,000/(24 * 120) microseconds).
Alternately, if a sequencer wishes to control playback independent of tempo, it can use Tick messages. These are sent at a rate of 1 message every 10 milliseconds. Of course, it is then up to the slave device to maintain and update its clock based upon these messages. The slave will be doing its own counting off of how many milliseconds are supposed to be in each "beat" at the current tempo.
The master needs to be able to start the slave precisely when the master starts. The master does this by sending a MIDI Start message. The MIDI Start message alerts the slave that, upon receipt of the very next MIDI Clock message, the slave should start the playback of its sequence. In other words, the MIDI Start puts the slave in "play mode", and the receipt of that first MIDI Clock marks the initial downbeat of the song (ie, MIDI Beat 0). What this means is that (typically) the master sends out that MIDI Clock "downbeat" immediately after the MIDI Start. (In practice, most masters allow a 1 millisecond interval inbetween the MIDI Start and subsequent MIDI Clock messages in order to give the slave an opportunity to prepare itself for playback). In essense, a MIDI Start is just a warning to let the slave know that the next MIDI Clock represents the downbeat, and playback is to start then. Of course, the slave then begins counting off subsequent MIDI Clock messages, with every 6th being a passing 16th note, every 12th being a passing eighth note, and every 24th being a passing quarter note.
A master stops the slave simultaneously by sending a MIDI Stop message. The master may then continue to send MIDI Clocks at the rate of its tempo, but the slave should ignore these, and not advance its "song position". Of course, the slave may use these continuing MIDI Clocks to ascertain what the master's tempo is at all times.
Sometimes, a musician will want to start the playback point somewhere other than at the beginning of a song (ie, he may be recording an overdub in a certain part of the song). The master needs to tell the slave what beat to cue playback to. The master does this by sending a Song Position Pointer message. The 2 data bytes in a Song Position Pointer are a 14-bit value that determines the MIDI Beat upon which to start playback. Sequences are always assumed to start on a MIDI Beat of 0 (ie, the downbeat). Each MIDI Beat spans 6 MIDI Clocks. In other words, each MIDI Beat is a 16th note (since there are 24 MIDI Clocks in a quarter note, therefore 4 MIDI Beats also fit in a quarter). So, a master can sync playback to a resolution of any particular 16th note.
For example, if a Song Position value of 8 is received, then a slave should cue playback to the third quarter note of the song. (8 MIDI beats * 6 MIDI clocks per MIDI beat = 48 MIDI Clocks. Since there are 24 MIDI Clocks in a quarter note, the first quarter occurs on a time of 0 MIDI Clocks, the second quarter note occurs upon the 24th MIDI Clock, and the third quarter note occurs on the 48th MIDI Clock).
A Song Position Pointer message should not be sent while the devices are in play. This message should only be sent while devices are stopped. Otherwise, a slave might take too long to cue its new start point and miss a MIDI Clock that it should be processing.
A MIDI Start always begins playback at MIDI Beat 0 (ie, the very beginning of the song). So, when a slave receives a MIDI Start, it automatically resets its "Song Position" to 0. If the master needs to start playback at some other point (as set by a Song Position Pointer message), then a MIDI Continue message is sent instead of MIDI Start. Like a MIDI Start, the MIDI Continue is immediately followed by a MIDI Clock "downbeat" in order to start playback then. The only difference with MIDI Continue is that this downbeat won't necessarily be the very start of the song. The downbeat will be at whichever point the playback was set via a Song Position Pointer message or at the point when a MIDI Stop message was sent (whichever message last occurred). What this implies is that a slave must always remember its "current song position" in terms of MIDI Beats. The slave should keep track of the nearest previous MIDI beat at which it stopped playback (ie, its stopped "Song Position"), in the anticipation that a MIDI Continue might be received next.
If a slave receives MIDI Start or MIDI Continue messages while it's in play, it should ignore those messages. Likewise, if it receives MIDI Stop messages while stopped, it ignores those.
---------------------------------------------
http://www.blitter.com/~russtopia/MIDI/~jglatt/tech/midispec/pressure.htm
What's the difference between AfterTouch and Channel Pressure? Well, AfterTouch messages are for individual keys (ie, an Aftertouch message only affects that one note whose number is in the message). Every key that you press down generates its own AfterTouch messages. If you press on one key harder than another, then the one key will generate AfterTouch messages with higher values than the other key. The net result is that some effect will be applied to the one key more than the other key. You have individual control over each key that you play. With Channel Pressure, one message is sent out for the entire keyboard. So, if you press one key harder than another, the module will average out the difference, and then just pretend that you're pressing both keys with the exact same pressure. The net result is that some effect gets applied to all sounding keys evenly. You don't have individual control per each key. A controller normally uses either Channel Pressure or AfterTouch, but usually not both. Most MIDI controllers don't generate AfterTouch because that requires a pressure sensor for each individual key on a MIDI keyboard, and this is an expensive feature to implement. For this reason, many cheaper units implement Channel Pressure instead of Aftertouch, as the former only requires one sensor for the entire keyboard's pressure. Of course, a device could implement both Aftertouch and Channel Pressure, in which case the Aftertouch messages for each individual key being held are generated, and then the average pressure is calculated and sent as Channel Pressure.
---------------------------------------------
http://www.blitter.com/~russtopia/MIDI/~jglatt/tech/midispec.htm
Aftertouch
Category: Voice
Purpose
While a particular note is playing, pressure can be applied to it. Many electronic keyboards have pressure sensing circuitry that can detect with how much force a musician is holding down a key. The musician can then vary this pressure, even while he continues to hold down the key (and the note continues sounding). The Aftertouch message conveys the amount of pressure on a key at a given point. Since the musician can be continually varying his pressure, devices that generate Aftertouch typically send out many such messages while the musician is varying his pressure. Upon receiving Aftertouch, many devices typically use the message to vary a note's VCA and/or VCF envelope sustain level, or control LFO amount and/or rate being applied to the note's sound generation circuitry. But, it's up to the device how it chooses to respond to received Aftertouch (if at all). If the device is a MultiTimbral unit, then each one of its Parts may respond differently (or not at all) to Aftertouch. The Part affected by a particular Aftertouch message is the one assigned to the message's MIDI channel.
It is recommended that Aftertouch default to controlling the LFO amount (ie, a vibrato effect).
Status
0xA0 to 0xAF where the low nibble is the MIDI channel.
Data
Two data bytes follow the Status.
The first data is the note number. There are 128 possible notes on a MIDI device, numbered 0 to 127 (where Middle C is note number 60). This indicates to which note the pressure is being applied.
The second data byte is the pressure amount, a value from 0 to 127 (where 127 is the most pressure).
Errata
See the remarks under Channel Pressure.
----------------------------------------
http://www.blitter.com/~russtopia/MIDI/~jglatt/tech/midispec.htm
Note Off
Category: Voice
Purpose
Indicates that a particular note should be released. Essentially, this means that the note stops sounding, but some patches might have a long VCA release time that needs to slowly fade the sound out. Additionally, the device's Hold Pedal controller may be on, in which case the note's release is postponed until the Hold Pedal is released. In any event, this message either causes the VCA to move into the release stage, or if the Hold Pedal is on, indicates that the note should be released (by the device automatically) when the Hold Pedal is turned off. If the device is a MultiTimbral unit, then each one of its Parts may respond to Note Offs on its own channel. The Part that responds to a particular Note Off message is the one assigned to the message's MIDI channel.
Status
0x80 to 0x8F where the low nibble is the MIDI channel.
Data
Two data bytes follow the Status.
The first data is the note number. There are 128 possible notes on a MIDI device, numbered 0 to 127 (where Middle C is note number 60). This indicates which note should be released.
The second data byte is the velocity, a value from 0 to 127. This indicates how quickly the note should be released (where 127 is the fastest). It's up to a MIDI device how it uses velocity information. Often velocity will be used to tailor the VCA release time. MIDI devices that can generate Note Off messages, but don't implement velocity features, will transmit Note Off messages with a preset velocity of 64.
Errata
An All Notes Off controller message can be used to turn off all notes for which a device received Note On messages (without having received respective Note Off messages).
-----------------------------------------------
Controller
Category: Voice
Purpose
Sets a particular controller's value. A controller is any switch, slider, knob, etc, that implements some function (usually) other than sounding or stopping notes (ie, which are the jobs of the Note On and Note Off messages respectively). There are 128 possible controllers on a MIDI device. These are numbered from 0 to 127. Some of these controller numbers are assigned to particular hardware controls on a MIDI device. For example, controller 1 is the Modulation Wheel. Other controller numbers are free to be arbitrarily interpreted by a MIDI device. For example, a drum box may have a slider controlling Tempo which it arbitrarily assigns to one of these free numbers. Then, when the drum box receives a Controller message with that controller number, it can adjust its tempo. A MIDI device need not have an actual physical control on it in order to respond to a particular controller. For example, even though a rack-mount sound module may not have a Mod Wheel on it, the module will likely still respond to and utilize Modulation controller messages to modify its sound. If the device is a MultiTimbral unit, then each one of its Parts may respond differently (or not at all) to various controller numbers. The Part affected by a particular controller message is the one assigned to the message's MIDI channel.
Status
0xB0 to 0xBF where the low nibble is the MIDI channel.
Data
Two data bytes follow the Status.
The first data is the controller number (0 to 127). This indicates which controller is affected by the received MIDI message.
The second data byte is the value to which the controller should be set, a value from 0 to 127.
Errata
An All Controllers Off controller message can be used to reset all controllers (that a MIDI device implements) to default values. For example, the Mod Wheel is reset to its "off" position upon receipt of this message.
See the list of Defined Controller Numbers for more information about particular controllers.
-------------------------------------------
Program Change
Category: Voice
Purpose
To cause the MIDI device to change to a particular Program (which some devices refer to as Patch, or Instrument, or Preset, or whatever). Most sound modules have a variety of instrumental sounds, such as Piano, and Guitar, and Trumpet, and Flute, etc. Each one of these instruments is contained in a Program. So, changing the Program changes the instrumental sound that the MIDI device uses when it plays Note On messages. Of course, other MIDI messages also may modify the current Program's (ie, instrument's) sound. But, the Program Change message actually selects which instrument currently plays. There are 128 possible program numbers, from 0 to 127. If the device is a MultiTimbral unit, then it usually can play 16 "Parts" at once, each receiving data upon its own MIDI channel. This message will then change the instrument sound for only that Part which is set to the message's MIDI channel.
For MIDI devices that don't have instrument sounds, such as a Reverb unit which may have several Preset "room algorithms" stored, the Program Change message is often used to select which Preset to use. As another example, a drum box may use Program Change to select a particular rhythm pattern (ie, drum beat).
Status
0xC0 to 0xCF where the low nibble is the MIDI channel.
Data
One data byte follows the status. It is the program number to change to, a number from 0 to 127.
Errata
On MIDI sound modules (ie, whose Programs are instrumental sounds), it became desirable to define a standard set of Programs in order to make sound modules more compatible. This specification is called General MIDI Standard.
Just like with MIDI channels 0 to 15 being displayed to a musician as channels 1 to 16, many MIDI devices display their Program numbers starting from 1 (even though a Program number of 0 in a Program Change message selects the first program in the device). On the other hand, this approach was never standardized, and some devices use vastly different schemes for the musician to select a Program. For example, some devices require the musician to specify a bank of Programs, and then select one within the bank (with each bank typically containing 8 to 10 Programs). So, the musician might specify the first Program as being bank 1, number 1. Nevertheless, a Program Change of number 0 would select that first Program.
Receipt of a Program Change should not cut off any notes that were previously triggered on the channel, and which are still sustaining.
-------------------------------------
Channel Pressure
Category: Voice
Purpose
While notes are playing, pressure can be applied to all of them. Many electronic keyboards have pressure sensing circuitry that can detect with how much force a musician is holding down keys. The musician can then vary this pressure, even while he continues to hold down the keys (and the notes continue sounding). The Channel Pressure message conveys the amount of overall (average) pressure on the keys at a given point. Since the musician can be continually varying his pressure, devices that generate Channel Pressure typically send out many such messages while the musician is varying his pressure. Upon receiving Channel Pressure, many devices typically use the message to vary all of the sounding notes' VCA and/or VCF envelope sustain levels, or control LFO amount and/or rate being applied to the notes' sound generation circuitry. But, it's up to the device how it chooses to respond to received Channel Pressure (if at all). If the device is a MultiTimbral unit, then each one of its Parts may respond differently (or not at all) to Channel Pressure. The Part affected by a particular Channel Pressure message is the one assigned to the message's MIDI channel.
It is recommended that Channel Pressure default to controlling the VCA level (ie, a volume swell/fade effect).
Status
0xD0 to 0xDF where the low nibble is the MIDI channel.
Data
One data byte follows the Status. It is the pressure amount, a value from 0 to 127 (where 127 is the most pressure).
--------------------------------------
Pitch Wheel
Category: Voice
Purpose
To set the Pitch Wheel value. The pitch wheel is used to slide a note's pitch up or down in cents (ie, fractions of a half-step). If the device is a MultiTimbral unit, then each one of its Parts may respond differently (or not at all) to Pitch Wheel. The Part affected by a particular Pitch Wheel message is the one assigned to the message's MIDI channel.
Status
0xE0 to 0xEF where the low nibble is the MIDI channel.
Data
Two data bytes follow the status. The two bytes should be combined together to form a 14-bit value. The first data byte's bits 0 to 6 are bits 0 to 6 of the 14-bit value. The second data byte's bits 0 to 6 are really bits 7 to 13 of the 14-bit value. In other words, assuming that a C program has the first byte in the variable First and the second data byte in the variable Second, here's how to combine them into a 14-bit value (actually 16-bit since most computer CPUs deal with 16-bit, not 14-bit, integers):
unsigned short CombineBytes(unsigned char First, unsigned char Second)
{
unsigned short _14bit;
_14bit = (unsigned short)Second;
_14bit <<= 7;
_14bit |= (unsigned short)First;
return(_14bit);
}
A combined value of 0x2000 is meant to indicate that the Pitch Wheel is centered (ie, the sounding notes aren't being transposed up or down). Higher values transpose pitch up, and lower values transpose pitch down.
Errata
The Pitch Wheel range is usually adjustable by the musician on each MIDI device. For example, although 0x2000 is always center position, on one MIDI device, a 0x3000 could transpose the pitch up a whole step, whereas on another device that may result in only a half step up. The GM spec recommends that MIDI devices default to using the entire range of possible Pitch Wheel message values (ie, 0x0000 to 0x3FFF) as +/- 2 half steps transposition (ie, 4 half-steps total range). The Pitch Wheel Range (or Sensitivity) is adjusted via an RPN controller message.
---------------------------------------
Here are the assigned Manufacturer ID numbers (ie, the second byte in a System Exclusive message):