root/branches/transport_redesign/TRANSPORT_REDESIGN_NOTES.gmb @ 920

Revision 920, 41.2 KB (checked in by gabriel@…, 4 years ago)

Add notes on redesign of Hydrogen class.

Line 
1NOTES ON REDESIGNING THE TRANSPORT IN HYDROGEN
2==============================================
3
4--------------------------------------------------------------------------------
5Date: Tue, 26 Aug 2008 12:42:47 -0500
6From: "Gabriel M. Beddingfield" <gabriel@teuton.org>
7To:  hydrogen-devel@lists.sourceforge.net
8Subject: Re: [Hydrogen-devel] The transport, BBT, and Jack
9
10Jakob Lund wrote:
11> The transport code we have now is really confusing and hard to understand - I
12> still think we have to reinvent it somewhat in order to get it working.
13
14Agreed.  However, we may want to try and put a half-way decent band-aid on it
15and release 0.9.4 before restructuring it.
16
17> In my opinion the transport code should be moved into the sequencer (the
18> Hydrogen instance), instead of being processed in the audio driver (this is
19
20Actually... that's where the code already is, isn't it?  And that's kind of the
21problem.  When H2 is the master, the JACK Transport is just copying that code
22(which seems to work OK).  When H2 is the slave, the JACK Transport code is
23trying to cleverly update that info.  It looks like the sequencer was designed
24as a standalone sequencer (master), and later it was retrofitted with a slave
25option.  So what we have is that even when Hydrogen is a transport slave, it
26*thinks* it's the master (in hydrogen.cpp).
27
28How about this: Create a transport interface that the H2Core::Hydrogen is
29*always* the slave to.  One that is focused on mapping ticks to samples in the
30current buffer.  Then, create different Transport implementations depending on
31the situations.
32
33SEQUENCER   ABSTRACT INTERFACE      IMPLEMENTATIONS       BACKEND INTERFACES
34=========   ==================      ===============      ====================
35
36 Hydrogen <--TransportInterface <--+-- MasterTransport --> JackTransportAdapter
37   |                               |                                  |
38   |                               +-- SlaveJackTransport <-- jackd <-+
39   V                               |
40AudioOutput                        +-- SlaveMTCTransport <-- MidiDriver
41                                   |
42                                   +-- SlaveOtherTransport
43                                   |
44                                   +-- MasterTrickedOutExperimentalTransport
45                                   |
46
47class TransportInterface
48{
49public:
50     virtual ~TransportInterface() {}
51
52     uint32_t getBufferOffsetFrameForTick(uint32_t tick) = 0;
53     // ... other methods ...
54};
55
56Then, H2Core::Hydrogen doesn't care about sample rates, frames-per-tick,
57samples-per-beat, tempo, or anything.  We just go through the Note queue and
58say, "What sample to I use for tick 575?"
59
60> perhaps cosmetic, but it would be nice to be able to act as MIDI time slave
61> and / or master at some point). Also, there are some state variables in
62
63See above.  Good suggestion.  I've been thinking about doing this with InConcert
64as well.
65
66> Tempo changes from Ardour (which is about the only app that I know is being a
67> `good master`, in your terms) are working OK in Song mode; as long as you make
68> sure that 1 bar == 1 pattern on the song timeline (especially important if the
69> meter changes as well -- i.e. in non - 4/4 time).
70
71Maybe I'm misunderstanding what you're saying... but isn't that sort of a given
72when using the transport?  ...that your time signatures need to line up?
73Gracefully handling a mismatch would be good -- but I think is sort of
74"undefined" by its very nature.
75
76> Pattern mode is a bit funny - it seems that tempo changes aren't handled as
77> well there. When you press 'play' the pattern starts from beat 1, but if you
78> press rewind and then play, it sounds like beat 1 is played twice !??!!
79
80I thought I heard that in Song mode, too, recently.  I was throwing a lot of
81wacked out tempo changes, though.
82
83Peace,
84Gabriel
85
86--------------------------------------------------------------------------------
87Date: Sun, 1 Mar 2009 03:55:59 +0100
88From: "m.wolkstein@gmx.de" <m.wolkstein@gmx.de>
89To: hydrogen-devel <hydrogen-devel@lists.sourceforge.net>
90Subject: Re: [Hydrogen-devel] Gabriel Beddingfield commited [851]: Fix
91 double-hit at start when JACK Transport Master.
92
93Am Sat, 28 Feb 2009 04:19:10 +0000
94schrieb hydrogen@alerts.assembla.com:
95
96>
97>
98> Fix double-hit at start when JACK Transport Master.
99> Commit from user: Gabriel Beddingfield
100>
101> For more details, visit: http://trac.assembla.com/hydrogen/changeset/851
102>
103> Space URL: http://www.assembla.com/spaces/hydrogen
104>
105> -------
106>
107
108hiho, the old game :-(.
109after commit 851 it's not possible to change tempo during play.
110if you use old tabtempo (altgr + back slash) or new beatcounter (coma)
111the transport position jumps around (during play).
112so no live tempochange as master will be possible. that's definitely
113not usable for this use. jack time master implementation based on this
114feature. so other applications can follow h2 also during tempo change.
115
116maybe we need a small unitest for some important features.
117
118here a small list over things (this is what i think) that transport
119have to do.
120
121-- as slave--
122* follow other master apps (e.g ardour) start, stop, pause, jump to
123  position
124* follow tempo changes from master ->( currently broken)
125* relocate correct loop position. e.g. use a 4 pattern long h2 song in
126  loop mode to record a 16 pattern long song in ardour
127
128--as master--
129* do the same as slave!! important
130* slave apps have to follow h2 like h2 follow master apps. play, stop,
131  relocate new position, tempo....
132* tempo change during playback without wrong relocation. (ticksize
133  will change during tempo change) ->( currently broken)
134 
135wolke
136
137--------------------------------------------------------------------------------
138Date: Sun, 01 Mar 2009 21:16:37 -0600
139From: "Gabriel M. Beddingfield" <gabriel@teuton.org>
140To: hydrogen-devel@lists.sourceforge.net
141Subject: Re: [Hydrogen-devel] Gabriel Beddingfield commited [851]: Fix double-hit
142 at start when JACK Transport Master.
143
144This is a multi-part message in MIME format.
145--------------000603030801070202020104
146Content-Type: text/plain; charset=us-ascii; format=flowed
147Content-Transfer-Encoding: 7bit
148
149m.wolkstein@gmx.de wrote:
150> h2 as slave
151> (song mode)
152> + no double hit at start. (start impulse from h2 or ardour)
153> + follow correct tempo changes from master (ardour)
154
155Actually, I'm getting double-hits at start after a few +/- button tempo changes.
156
157> (song mode)
158> h2 as master
159> + no double hit at start. (start impulse from h2 or ardour)
160> + no jumping in timeline on tempochanging during playback.
161
162Yes, but...
163
164>
165> so imoh,
166> we can remove the whole getArdourTransportAdjustment function. because ardour
167> 2.7.1 and 3 , qjackctl(transport buttons), and seq24 produce no double hit
168> anymore.
169
170If everyone else agrees that our users only plan to use the H2 transport with
171Ardour 2.7.1 and 3.x... then yes, let's take out the transport adjustment.
172
173> only time master will need on two places - getBufferSize() what is the same
174> than getArdourTransportAdjustment.
175
176No.  This is wrong.
177
178There should be no buffersize correction.  Frame=0 should be 1:1:0... not
179Frame=getBufferSize().  Any sort of buffersize correction like this is *not*
180conforming to the transport and is working around some other bug.
181
182I did some transport auditing, and find that Hydrogen (as transport master)
183isn't working right at all -- independent of audio.  I wrote a JACK Client that
184just observes the jack_position_t that is being fed to all the JACK Clients (see
185attached).  Here's what we get with your patch:
186
187usecs=179033476580 fps=48000 frame=0 bpm=110.4 B:B:T=1:1:0 bbt_offset=0
188usecs=179033497885 fps=48000 frame=1024 bpm=110.4 B:B:T=1:1:0 bbt_offset=0
189usecs=179033519235 fps=48000 frame=2048 bpm=110.4 B:B:T=1:1:40 bbt_offset=0
190usecs=179033540554 fps=48000 frame=3072 bpm=110.4 B:B:T=1:1:48 bbt_offset=0
191usecs=179033561939 fps=48000 frame=4096 bpm=110.4 B:B:T=1:1:56 bbt_offset=0
192
193Notice that ticks 0 and 1024 are both 1:1:0, and that 2048 jumps to 1:1:40.  I
194would expect the ticks to go 0, 8, 16, etc.
195
196Here's rev 858:
197
198usecs=179454108209 fps=48000 frame=0 bpm=120 B:B:T=1:1:0 bbt_offset=0
199usecs=179454129526 fps=48000 frame=1024 bpm=120 B:B:T=1:1:0 bbt_offset=0
200usecs=179454150858 fps=48000 frame=2048 bpm=120 B:B:T=1:1:52 bbt_offset=0
201usecs=179454172379 fps=48000 frame=3072 bpm=120 B:B:T=1:1:60 bbt_offset=0
202usecs=179454193586 fps=48000 frame=4096 bpm=120 B:B:T=1:1:68 bbt_offset=0
203
204...not much better.
205
206What's more... the tick count is usually 8 ticks per period.  But, with your
207patch we sometimes get a period with only 4 ticks (1:2:76 -> 1:2:80):
208
209usecs=179034095196 fps=48000 frame=29696 bpm=110.4 B:B:T=1:2:52 bbt_offset=0
210usecs=179034118910 fps=48000 frame=30720 bpm=110.4 B:B:T=1:2:60 bbt_offset=0
211usecs=179034138749 fps=48000 frame=31744 bpm=110.4 B:B:T=1:2:68 bbt_offset=0
212usecs=179034160514 fps=48000 frame=32768 bpm=110.4 B:B:T=1:2:76 bbt_offset=0
213usecs=179034181174 fps=48000 frame=33792 bpm=110.4 B:B:T=1:2:80 bbt_offset=0
214usecs=179034201904 fps=48000 frame=34816 bpm=110.4 B:B:T=1:2:88 bbt_offset=0
215usecs=179034223196 fps=48000 frame=35840 bpm=110.4 B:B:T=1:2:96 bbt_offset=0
216
217This happens regularly.  Some other patches may do this, but I didn't see it
218with rev 858.
219
220In contrast, I get this from InConcert:
221
222usecs=180994287332 fps=48000 frame=0 bpm=120 B:B:T=1:1:0 bbt_offset=0
223usecs=180994308692 fps=48000 frame=1024 bpm=120 B:B:T=1:1:15 bbt_offset=23
224usecs=180994330006 fps=48000 frame=2048 bpm=120 B:B:T=1:1:30 bbt_offset=47
225usecs=180994351333 fps=48000 frame=3072 bpm=120 B:B:T=1:1:46 bbt_offset=5
226usecs=180994372664 fps=48000 frame=4096 bpm=120 B:B:T=1:1:61 bbt_offset=29
227
228(...not that InConcert is a pillar of stability and reliability... but... you
229get the point.)
230
231*sigh*  I'm not sure what to do.  :-/
232
233I'm backing out rev 851 to find a better solution.
234
235Peace,
236Gabriel
237
238
239
240
241
242--------------000603030801070202020104
243Content-Type: text/x-c++src;
244 name="t_log_xport.cpp"
245Content-Transfer-Encoding: 7bit
246Content-Disposition: inline;
247 filename="t_log_xport.cpp"
248
249/**********************************************-*- indent-tabs-mode:nil; -*-
250 *                                                                         *
251 *   Jack Transport Audit Utils                                            *
252 *                                                                         *
253 *   Copyright (C) 2008 by Gabriel M. Beddingfield                         *
254 *   gabriel@teuton.org                                                    *
255 *                                                                         *
256 *   "For of him [God], and through him, and unto him, are all things.     *
257 *   To him be the glory for ever. Amen." (Romans 11:36)                   *
258 *                                                                         *
259 *   This program is free software; you can redistribute it and/or modify  *
260 *   it under the terms of the GNU General Public License as published by  *
261 *   the Free Software Foundation; version 2 of the License, or any later  *
262 *   version                                                               *
263 *                                                                         *
264 *   This program is distributed in the hope that it will be useful,       *
265 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
266 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
267 *   GNU General Public License for more details.                          *
268 *                                                                         *
269 *   You should have received a copy of the GNU General Public License     *
270 *   along with this program; if not, write to the                         *
271 *   Free Software Foundation, Inc.,                                       *
272 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
273 ***************************************************************************/
274
275/* t_log_xport.cpp
276 *
277 * JACK Transport client that logs the frame and BBT of the transport
278 * for every process cycle.  This is similar to the showtime.c
279 * example client in the jack sources... but this catches every
280 * frame.  It records up to 262144 process cycles, it only records
281 * while Rolling.
282 */
283
284#include <cstring>
285#include <unistd.h>
286#include <iostream>
287#include <iomanip>
288#include <jack/jack.h>
289#include <jack/transport.h>
290
291using namespace std;
292
293struct xpos_t {
294    jack_time_t usecs;
295    jack_nframes_t frame_rate;
296    jack_nframes_t frame;
297    int32_t bar, beat, tick;
298    jack_nframes_t bbt_offset;
299    double beats_per_minute;
300};
301
302jack_client_t *_client;
303const long int BUFSIZE = 262144;  // max recording space.
304xpos_t buf[BUFSIZE];
305unsigned long buf_pos = 0;
306bool done = false;
307
308int jack_callback (jack_nframes_t nframes, void* /*arg*/)
309{
310    jack_transport_state_t     state;
311    jack_position_t            posit;
312
313    if( buf_pos >= (unsigned long)BUFSIZE ) {
314        done = true;
315    }
316
317    if(done) return 0;
318
319    state = jack_transport_query (_client, &posit);
320
321    if (state == JackTransportRolling)
322    {
323        buf[buf_pos].usecs = posit.usecs;
324        buf[buf_pos].frame_rate = posit.frame_rate;
325        buf[buf_pos].frame = posit.frame;
326        buf[buf_pos].bar = posit.bar;
327        buf[buf_pos].beat = posit.beat;
328        buf[buf_pos].tick = posit.tick;
329        buf[buf_pos].bbt_offset =
330            ((posit.valid & JackBBTFrameOffset) ? posit.bbt_offset : 0);
331        buf[buf_pos].beats_per_minute = posit.beats_per_minute;
332        ++buf_pos;
333    }
334
335    return 0; 
336}
337
338ostream& operator<<(ostream& os, xpos_t p)
339{
340    os << "usecs=" << p.usecs
341       << " fps=" << p.frame_rate
342       << " frame=" << p.frame
343       << " bpm=" << p.beats_per_minute
344       << " B:B:T=" << p.bar << ':' << p.beat << ':' << p.tick
345       << " bbt_offset=" << p.bbt_offset;
346    return os;
347}
348
349int main(void)
350{
351    jack_client_t *client;
352
353    memset (buf, 0, BUFSIZE * sizeof (float));
354
355    _client = jack_client_open(__FILE__,
356                              JackNullOption,
357                              0);
358    jack_set_process_callback(_client,
359                              jack_callback,
360                              0);
361                                 
362    jack_activate(_client);
363
364    unsigned long k = 0, tmp;
365    while(!done) {
366        sleep(1);
367        if( (buf_pos - k) >= 30 ) {
368            tmp = buf_pos;
369            for( k ; k < tmp ; ++k ) {
370                cout << buf[k] << endl;
371            }
372        }
373    }
374
375    done = true;
376
377    jack_client_close(_client);
378
379    return 0;
380}
381
382--------------000603030801070202020104--
383
384--------------------------------------------------------------------------------
385Date: Mon, 02 Mar 2009 13:03:48 -0600
386From: "Gabriel M. Beddingfield" <gabriel@teuton.org>
387To: hydrogen-devel@lists.sourceforge.net
388Subject: Re: [Hydrogen-devel] Gabriel Beddingfield commited [851]: Fix double-hit
389 at start when JACK Transport Master.
390
391m.wolkstein@gmx.de wrote:
392
393>> If everyone else agrees that our users only plan to use the H2 transport with
394>> Ardour 2.7.1 and 3.x... then yes, let's take out the transport adjustment.
395
396> yes, imo people who build and use actually versions of h2 also do this also
397> with other software.
398> also, ardour 2.7.x is in this moment standard in most distribution's. so i
399> think it's ok when h2 0.9.4 maybe in 1 - 9 month work stable with this and
400> newer versions.
401
402Sorry, I wrote that in a somewhat intimidating manner, didn't I?  What I mean is
403this: if we all agree to *not* support Ardour's bug, then I'm OK with taking it
404out.
405
406However, I don't agree to doing any buffer offsets -- unless we can turn them off.
407
408> if you interpret the code you will see that bbt 110 will set on 0 -
409> buffersize. that produce a negative value.
410>
411> hmm?. the function getArdourTransportAdjustment() returns getBufferSize().
412
413> so what is wrong if i say it's the same. i write "-" get buffersize and not =
414> get buffersize. i only relpace getArdourTransportAdjustment() with
415> getBufferSize(). because imo, this pref. option is deprecated and what do
416> exactly the same thing but without test the pref. settings!!  and i remove
417> this buffersize offset in slave function. so frame 0 = 0 and not 0 - buffer
418> offset. what works correct for the moment.
419
420The problem is that adding or subtracting any offset violates the principles of
421using the transport in the first place.  The only Right reason for doing it is
422for latency compensation.  There should be ZERO latency between H2 and Ardour.
423Transport frame 0 in H2 is transport frame 0 in Ardour.  If not -- somone is
424cheating.
425
426The buffer offset says that frame 1024 in Ardour is frame 0 in H2.  This is
427wrong.  (That is, if you start jack with 1024 frames/period.)
428
429> sorry but what mean all the output? and when you get it? after tempochange? or
430> after start?
431
432It "records" the transport state whenever it is rolling.  It doesn't care about
433tempo changes or anything.  When someone presses PLAY, it records.  When someone
434pressed STOP, it waits.
435
436> also sound card samplerate is never exact. what means samplerate is a product
437> from any divider from sound card quartz oscillator (if they have already
438> one). so what can i do with usecs and fps which are not exact? and also
439> sampler.cpp compute all output stream in the buffer size time.  did your
440> simple client incorporate this? don't misunderstand me but i try to learn how
441> the timing function works.
442
443Samplerate has nothing to do with it.  You probably know that whenever JACK does
444the process() callback, all we care about is the next nFrames samples.  We don't
445care about the oscillators in the hardware, or what the time on the wall clock
446says... we just care about reading or writing the next nFrames samples.  The
447only reason why we care about the sample rate is so that we can resample our
448waveforms.
449
450Usecs is simply provided by the JACK server in the jack_position_t.usecs field.
451  All my little test program does is record what that value was.  I included it
452because there may be cases it helps to understand what's happening in the data
453(but none of these cases really benefits from usecs).
454
455> in moment i think to get a sync start h2 and other clients or masters always
456> need (or have to use) this given buffersize to compute the output.  so if h2
457> plans to start the transport it must send a correct startframe. e.g to
458> ardour. and that ardour can begin transport exactly h2 have to wait one
459> buffersize. because ardour need and use this time to compute the right
460> output. so if h2 is master hydrogen have to correct the "internal" transport
461> to startframe - buffersize. if h2 is slave and you start also transport from
462> h2 all exact information will compute in master this includes also the
463> buffersize offset. so imo, this will work correct in all ardour >2.7.x. i
464> don't know the sample function in ardour. but i think this function s really
465> complex. to compute all the things you can do in ardour.
466
467No, this is handled by the transport controls.  Whoever calls
468jack_transport_locate() decides what the start frame is.  Hydrogen and Ardour
469are supposed to play whatever happens at frame # jack_position_t.frame.  (In
470other words, H2 and Ardour should have perfectly synchronized frames:  0, 1024,
4712048, etc...)
472
473> the other way to handle a exact sync is to send only a whatever control signal
474> to all clients and than after one period buffersize all clients and master
475> start or do whatever control signal will send. but this method will cost 2
476> periods of buffersize. one period for sending the signal from any client or
477> master to any clients or master. and a second period to sync the output of all
478> the client and master sample engine. so imo, this is not the way how jack
479> transport work. imo, in moment to sync all apps will only cost 1 periode
480> buffersize from the trigger moment. what implement to compute the offset in
481> one of the apps. mostly master app.
482> my patch use buffersize offset as master and "no offset" as salve
483
484Did you know:  There is *no* callback whenever the transport master CHANGES?  If
485H2 is the transport master... but Ardour takes over -- there is *no*
486notification of the change.  So, if Ardour takes over as master... but H2 still
487*thinks* he's the master --- we're screwed.  Right?  We're making decisions
488based on who's controlling the transport.  (I discovered this in the last few days.)
489
490We're not supposed to care who the transport master is.  We're not supposed to
491make decisions based on who's controlling the transport.
492
493>> Here's rev 858:
494>>
495>> usecs=179454108209 fps=48000 frame=0 bpm=120 B:B:T=1:1:0 bbt_offset=0
496>> usecs=179454129526 fps=48000 frame=1024 bpm=120 B:B:T=1:1:0 bbt_offset=0
497>> usecs=179454150858 fps=48000 frame=2048 bpm=120 B:B:T=1:1:52 bbt_offset=0
498>> usecs=179454172379 fps=48000 frame=3072 bpm=120 B:B:T=1:1:60 bbt_offset=0
499>> usecs=179454193586 fps=48000 frame=4096 bpm=120 B:B:T=1:1:68 bbt_offset=0
500>>
501>> ...not much better.
502>>
503>> What's more... the tick count is usually 8 ticks per period.  But, with your
504>> patch we sometimes get a period with only 4 ticks (1:2:76 -> 1:2:80):
505
506> do you read the jack transport bbt and jack transport frames. because h2 ticks
507> from jack time master are not the same than h2 internal transport ticks.
508
509Hydrogen was the transport master for this test.  The BBT given is whatever
510Hydrogen wrote to the jack_position_t struct.  Hydrogen wrote bpm, bar, beat,
511and tick.  (H2 doesn't supply bbt_offset, so it's assumed 0, per the Jack docs.)
512
513One would expect that BBT changes at an even pace if the tempo is not changing.
514  Hydrogen does not do this at startup.
515
516> i am wrong if your displayed frames are not the jack transport frames. also
517> all usec values differ around +/- 300 usec.
518
519usecs is supplied by the JACK server.
520
521>> Peace,
522>> Gabriel
523>
524> also peace wolke :-)
525
526:-)
527
528-gabriel
529
530--------------------------------------------------------------------------------
5312009-03-03 TRANSPORT DESIGN CONCEPTS
532====================================
533
534     HydrogenGUI
535        A
536        |                                              Current Song
537        V                                                     |(**)
538TransportControlInterface                JackTimebaseCallback |
539        A (start, stop, locate, select              A         |
540        |  transport master backend)                |(**)     |
541        V                                   (++)    |         V
542  Transport <-- TransportMasterInterface <-+-InternalTransportMaster
543     | (private)                           |
544     |                                     +-JackTransportMaster (++)
545     V                                     |                     
546TransportPosition (Struct/class)           +-MiscTransportMaster
547            | (analog to jack_position_t)  |
548            |                              .
549            V                              .
550    Hydrogen (sequencer)                   .
551                        (**) Currently, the Current song is a module
552                             variable (private) for hydrogen.cpp.
553                             How do we expose this to the transport?
554                        (++) Someone, somewhere, has to compensate
555                             for these cases:
556                             * When jack_position_t does not have BBT.
557                             * When ticks don't match Hydrogen's ticks.
558
559When H2 uses the Jack Transport, it will *always* slave to the
560JackTransportMaster.  When H2 is the JACK transport master, then
561InternalTransportMaster will be used for the external transport's
562callback.  However, we'll still slave to JackTransportMaster (whether
563we think we're in control or not).
564
565The intention when slaving to the JACK transport is that we listen to
566the BBT coming from the transport (only) -- if it is provided.  If it
567is *not* provided, then we need some manner of fallback.
568
569InternalTransportMaster is just a concept for now.  The plan is to
570start off with a simple implementation (i.e. no tap-tempo).  Once
571working, we can provide some tap-tempo models.
572
573Also, by modularizing these, it's much easier to write unit tests to
574ensure that the transports will follow certain rules.
575
576The names "Master" are a little confusing when you think in terms of being the
577Jack Transport Master.
578
579--------------------------------------------------------------------------------
5802009-03-05 SEQUENCER AND SAMPLER
581================================
582
583ROLES.....
584
585The sequencer (H2Core::Hydrogen) looks at the timeline (Transport) and the song
586and schedules sounds to be made by the synthesizer (Sampler).  The sampler is
587responsible for organizing and maintaining the current drum kit.  When
588controlling the sampler, it should not need any information about bars, beats,
589ticks, or the song.  Instead, the sequencer schedules samples to be triggered at
590specific frame numbers.
591
592MUTING.....
593
594So then, who is responsible for *MUTING*?  It could go either way.  If an
595instrument is muted, the sampler could ignore any triggers for that instrument.
596On the other hand, if the instrument is muted then the sequencer could *not*
597schedule the trigger.
598
599If muting is handled by the sampler, it's possible that muted channels can still
600have their signals registered on the meters.  All the normal processing is done,
601but the final signal is not mixed.  Or, if no processing is to be done... that
602can still be done efficiently be the sampler.
603
604On the other hand, if muting is handled by the sequencer then scheduling notes
605for the sampler is nearly identical to scheduling notes for MIDI output.
606
607CURRENTLY, Hydrogen does not show meters on a muted channel.
608
609      THEREFORE:  Muting will be handled by the sequencer.
610
611AUDIO DRIVERS.......
612
613What, then, should be the relationship between the sampler and the audio
614driver(s).  Currently, the audio drivers are owned and operated by the sequencer
615(H2Core::Hydrogen)... or maybe that's what H2Core::AudioEngine is trying to do.
616So, the sampler is handled like this:
617
618    * Sequencer shares an output buffer with the audio output driver.
619
620    * Sampler has its own buffer.
621
622    * Sequencer sends a series of note_on() events to the sequencer.  The
623      sequencer queues these notes... but doesn't play them.  The queue is
624      scheduled by tick (I think).
625
626    * Sequencer calls Sampler::process() to render all of the notes.
627
628    * Sequencer copies the sampler's buffers to the sequencer's main buffers.
629
630    * Sequencer used to do the same thing with the subtractive synth.... adding
631      in the synth's buffers to the main buffers.  (I just deleted that.)
632
633    * Sequencer then processes the main outs through LADSPA effects... using a
634      similar pattern.
635
636I would be inclined to say that the Sequencer is having way too much to do with
637the audio... but someobody's got to be coordinating things.  However, it might
638make more sense (and reduce buffers) if the Sampler was handling things with the
639audio driver.  This would mean that the effects, too, would fall under the
640domain of the Sampler.
641
642It really comes down to what is more flexable... but keeps the parts separate.
643
644IMHO, the sampler is our synthesizer... and needs to have a close
645relationship with the audio driver.
646
647      THEREFORE:  FX and audio outs will be handled by the Sampler.
648
649NOTE OFF EVENTS..........
650
651There's been two other devs working on handling note off stuff (including
652Michael Wolkstein).  These should also be scheduled by the sequencer.  How they
653are handled by the sampler is instrument-dependent.
654
655Since there appears to be a long history of H2 songs setting the length to
656-1... we should also establish some manner of "default note length."  I don't
657recall what the MIDI standard says about repeat notes... is it OK to send
658multiple note-on's but only one note-off?  How do we suppress the first
659note-off?
660
661Answer: MIDI assumes that when a note is on, it's on... and off is
662off.  It doesn't consider having the same note on twice at the same
663time (unison).  Therefore, there's no requirements about NOTE ON's
664being preceeded by NOTE OFF's.  Likewise, a NOTE OFF can be sent at
665any time, and will have no effect if the note is already off.
666So... when the sequencer schedules a note to re-trigger... it needs to
667cancel any other NOTE OFF events that have been scheduled for that
668not.
669
670There shall be no "max" note length.
671
672      THEREFORE: Note off messages handled by sequencer.  Notes
673                 re-triggered before the NOTE OFF will have the prior
674                 NOTE OFF event cancelled.
675
676SAMPLER EVENT SEQUENCE......
677
678To communicate events to the sampler, the sequencer will create a script (event
679list) that is shared with the sampler.  This event list is essentially
680translating the song B:b.t into frame numbers.  The list will persist between
681process() cycles... and should probably be some manner of ring buffer.
682
683In this script, frame 0 will refer to the first frame of the current process()
684cycle.  When the sequencer is scheduling... if a song tick could overlap into
685the current process() cycle (through lead/lag/humanize)... then the sequencer
686may schedule that tick.  This means that THE SEQUENCER CAN SCHEDULE MORE THAN
687ONE PERIOD'S WORTH OF EVENTS.  How many periods it could need to schedule can be
688determined based on the max size of lead, lag, and humanize.
689
690Because frames will be scheduled relative to the current process() cycle, this
691means that frame numbers have to be re-normalized after every cycle.  Otherwise,
692some other scheme would need to be implemented.  (e.g. frames scheduled relative
693to some number that may or may not correspond to the real transport frame.)
694
695Since there may be many ways to optimize this scheme... I wonder if it would be
696worth giving it some manner of container sematics.  Then, we can change the
697implementation without having to mess with the sequencer or the sampler.  But,
698this may be over-thinking it.
699
700       THEREFORE: Sequencer will communicate events to the sampler
701                  through a sorted list of events that are indexed by
702                  the frame offset for the current process cycle.
703                  More than one cycle may be scheduled, and so the
704                  frame refs will have to be readjusted after every
705                  cycle.
706
707SORTING.........
708
709Seems that someone needs to sort the events in the event sequence.  Right now,
710it's delegated to a priority_queue... but I think that actually requires memory
711allocation in realtime sections.
712
713WITHOUT sorting, it will require several passes over the audio buffers in the
714sequencer... and maybe several passes over the event sequence.  This is
715essentially sorting.
716
717WITH sorting... it can be done in any of these places: the sequencer, the event
718list (e.g. a priority queue), or the sampler.
719
720I would prefer that the event list sort itself... or...
721
722Or perhaps have the sequencer just dump all the events and sort them at one time
723using some (efficient) manner of pointer redirections... or...
724
725       THEREFORE: A sampler event list object will be created.  It
726                  will be sorted and provide an STL-container-like
727                  interface.  The implementation will be hidden so
728                  that the underlying storage and implementations are
729                  hidden (and can be changed without breaking
730                  anything).
731
732TRANSPORT POSITION.....
733
734I've already written a bunch of code using ticks_per_beat and
735beats_per_bar as floating point types.  Need to decide now if these
736will become integers.
737
738Calculation efficiency is not much different between FDIV, DIV, and
739IDIV instructions on an x86 (DIV is a little faster).  So, it ends up
740being an issue of clarity and flexibility.  If we snap these to be
741integers... we'll need to negotiate with JACK about when they're *not*
742integers.  If we don't snap them to integers, we need to handle them
743*without* the assumption that they *do* snap to integers.
744
745Here's the current (as of this writing) TransportPosition struct:
746
747    struct TransportPosition
748    {
749        enum { STOPPED, ROLLING } state; /// The current transport state.
750        uint32_t frame;           /// The current frame of the transport.  When
751                                  /// sequencing, this is just FYI.  All
752                                  /// sequencing shall be done based on the
753                                  /// other fields (esp. B:b:t).
754        uint32_t frame_rate;      /// The audio sample rate (frames per second)
755        int32_t bar;              /// The current measure (1, 2, 3...)
756        int32_t beat;             /// The current beat in measure (1, 2, 3...)
757        int32_t tick;             /// The current tick in beat (0, 1, 2...)
758        uint32_t bbt_offset;      /// bar, beat, and tick refer to bbt_offset
759                                  /// frames BEFORE the current process cycle.
760
761        double bar_start_tick;    /// Absolute number of ticks elapsed in song
762                                  /// at the start of this bar.
763        float beats_per_bar;      /// The top number in the time signature
764        float beat_type;          /// The bottom number in the time signature
765        double ticks_per_beat;    /// Number of ticks in a single beat
766        double beats_per_minute;  /// The song tempo (beats per minute)
767    };
768
769These are close analog's to JACK's jack_position_t.  Paul Davis
770explained it like this:
771
772-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
773Date: Wed, 4 Mar 2009 07:46:03 -0500
774From: Paul Davis <paul@linuxaudiosystems.com>
775To: "Gabriel M. Beddingfield" <gabriel@teuton.org>
776Cc: JACK <jack-devel@lists.jackaudio.org>
777Subject: Re: [Jack-Devel] jack_transport_reposition() and the JackPositionBBT
778        fields
779
780On Wed, Mar 4, 2009 at 12:45 AM, Gabriel M. Beddingfield <gabriel@teuton.org
781> wrote:
782
783>
784> 3. In the jack_position_t BBT fields... why are bar_start_tick,
785> beats_per_bar, beat_type, and ticks_per_beat all floating point types?  I
786> would have expected unsigned integers for all of these.  Should I set up to
787> handle ticks_per_beat = 67.174?
788
789
790there are musical traditions around the world in which beats_per_bar and the
791subdivisions of a bar into beats cannot be properly represented with
792integers. indian and some south-east asian traditions (bali and thailand)
793contain music in which it makes sense to think of a meter as containing half
794beats, for example. it doesn't work to just double the tempo and thus move
795to a whole number of beats - this misses the subtlety of the shifting
796relationship between the rhythmic and other components of the music.
797
798ticks per beat is normally a mid-size integer value that represents "BBT
799resolution". it is typically a number with a large number of factors, which
800thus allows 1 beat to be divided in many different ways and still end up
801with an integral number of ticks. ardour, for example, uses 1960 ticks per
802beat, which has a very large set of numbers as factors. this means that you
803can divide a beat into, say, 8ths, 10th, 12ths and so on, and each division
804is an exact number of ticks. all MIDI sequencers do this, since its a way to
805avoid rounding errors.
806
807even though this number is always going to be an integer, to harmonize with
808the floating point values of beats per bar and beat type, it was defined as
809a floating point value.
810
811-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
812
813Now, in Hydrogen... in the past... it's worked like this...
814
815    struct TransportPosition
816    {
817        // [SNIP]
818
819        float beats_per_bar;      /// Always ticks_per_beat / 48.0
820        unsigned beat_type;       /// Always 4
821        unsigned ticks_per_beat;  /// Always 48
822        double beats_per_minute;  /// The song tempo (beats per minute)
823    };
824
825I think beats_per_bar and beats_per_minute remain floating points.
826
827If we allow beat_type to be floating point, then (as Paul said),
828ticks_per_beat would need to be a floating point to match.  Suppose
829the time signature is something really random like 6.666/3.5, with
8301680.0 ticks per beat.  Every measure would have:
831
832    6.666 * 1680.0 = 11198.88 ticks/bar
833
834If we calculate bar start ticks by rounding to 11199
835ticks/bar... roundoff error will catch up to us.
836
837HOWEVER, Hydrogen is all about pattern based sequencing... so...
838
839    THEREFORE: beats_per_bar, beat_type, ticks_per_beat, and
840               bar_start_tick will all be unsigned integer types.
841
842--------------------------------------------------------------------------------
8432009-03-16 SEQUENCER THRU AUDIO OUT
844===================================
845
846Sequencer
847 |    |
848 |    +----> (Other outputs)
849 |
850 | (Via SeqScript)
851 |
852 V
853Sampler
854 |   (routing)
855 +-+-+-+-+-+ ... +-+-+-+
856 | | | | | |     | | | |
857 (I n s t r u m e n t s)
858 | | | | | |     | | | |
859 V V V V V V     V V V V
860.........................
861.                       .<--> FX1 SEND/RETURN
862.                       .<--> FX2 SEND/RETURN
863.       Mixer           .<--> FX3 SEND/RETURN
864.                       .<--> FX4 SEND/RETURN
865.                       .
866.                       .<--> Anything else
867.........................
868 | |  | | | | | | |
869 L R  TRACKING-OUTS
870 | |  | | | | | | |
871 V V  V V V V V V V
872.....................
873.   Audio Driver    .
874.....................
875
876This causes the mixer to have a ton of buffers.  But, with "tracking outs," I
877don't think there's much way to avoid it.
878
879One way to reduce the buffers is to provide an interface for the instruments to
880write directly to mixer buffers with desired gains.
881
882int Instrument::process(nframes)
883{
884    float atten = 1.0;
885    sample_type* out;
886    Mixer* M;
887    //...
888    out = M->get_buffer_for_input(this);
889    atten = M->get_fader_atten_for_input(this);
890    end = out + nframes;
891    while(out != end) {
892        (*out) += atten * this->sample; // Write directly to mixer's buffer.
893        ++out;
894    }
895
896    return 0;
897}
898
899But... things change for tracking outs.
900
901-- or do they?  We still ask the mixer for a pointer to the buffer where the
902data goes.  If the mixer wants direct outs, the atten will be something like
9031/32.  If the mixer handles tracking outs, it will be 1.0.
904
905But... this setup feels like we're wagging the dog.  The call stack is something
906like:
907
908jackd process()
909  Hydrogen::process()
910    Sequencer::process()
911      Sampler::process()
912        Instrument::process()
913          Mixer::get_buffer()
914
915Seems like the Mixer should be in control of the instruments, instead of the
916Instruments being in control.  But how do we do that w/o the mixer being part of
917the sampler (thus, not a generic mixer)?
918
919But -- it doesn't seem like it would be too difficult to optimize tracks like
920this -- with inst's that aren't playing assumed to be zero and skipped.
921
922Here's a target Mixer API:
923
924int Hydrogen::process(nframes)
925{
926    Sequencer* seq;
927    Mixer* mix;
928    Transport* xport;
929    TransportPosition xpos;
930
931    xport->get_position(xpos);
932    seq->process(xpos, nframes);
933    mix->process(nframes);        // Is 'nframes' necc.?
934
935    return 0;
936}
937
938int SomeSeqClient::process(beg, end, xpos, nframes)
939{
940    Mixer* mix;
941    sample_type* obuf;
942
943    mix = Hydrogen::get_instance()::get_mixer();  // How do we avoid invoking
944                                                  // the Singleton here?
945    obuf = mix->get_buf_for_input(this);
946
947    // write stuff to obuf
948
949    return 0;
950}
951
952class Mixer
953{
954    //...
955
956private:
957    some_sequence_type<SeqClient*> used;
958    some_map_type<SeqClient* sample_type*> bufs;
959
960    //...
961};
962
963sample_type* Mixer::get_buf_for_input(that)
964{
965    used.push_back(that);
966    return bufs[that];
967}
968
969int Mixer::process(nframes)
970{
971    sample_type* outL, outR, that;
972
973    //...
974
975    while( ! used.empty() ) {
976        that = bufs[used.front()];
977        used.pop();
978        for(uint32_t k=0; k<nframes; ++k) {
979            outL[k] += that[k];
980            outR[k] += that[k];
981        } // what about L/R pan?
982    }
983
984    return 0;
985}
986
987--------------------------------------------------------------------------------
9882009-03-22 REVISED ROLE OF H2Core::Hydrogen
989===========================================
990
991The Hydrogen class has become a combination of the sequencer, audio engine, and
992controller for the audio backend.  That seems to me like too much.  The new
993Hydrogen class will simply be a controller for the audio engine.
994
995Note that this here is more of a concept.  I'm not married to the details (such
996as using std::string as the generic driver identifier).  Also, I'd prefer to
997take "Input" and "Output" out of the audio and midi drivers.  We generally use
998the same driver for both the input and output side.
999
1000class Hydrogen : public Object
1001{
1002public:
1003        typedef std::string driver_id_t;
1004        typedef std::deque<driver_id_t> driver_list_t;
1005
1006        /// Return the Hydrogen instance
1007        static Hydrogen* get_instance();
1008
1009        ~Hydrogen();
1010
1011        /// Access to major objects.
1012        TransportMasterInterface& get_transport();
1013        Mixer& get_mixer();
1014        Sequencer& get_sequencer();
1015        Sampler& get_sampler();
1016        AudioOutput& get_audiooutput();
1017        MidiInput& get_midiinput();
1018
1019        /// Should LADSPA effects be handled by Hydrogen or the Mixer?
1020
1021        /// Song manipulation
1022        /// Should Hydrogen or GUI be loading songs??
1023        Song& get_song();
1024        void set_song(Song* s);
1025        void set_song(std::string path);
1026        void remove_song();
1027
1028        /// Configuration options
1029        const driver_list_t& get_transport_drivers();
1030        int set_transport_driver(const driver_id_t& name);
1031        const driver_list_t& get_audio_outputs();
1032        int set_audio_output(const driver_id_t& name);
1033        const driver_list_t& get_midi_drivers();
1034        int set_midi_input(const driver_id_t& name);   
1035
1036        /// Future ideas for interfaces.  We could feed the
1037        /// GUI a list of the configuration parameters by driver.
1038        /// The GUI would create the config dialog based on this
1039        /// list.  This way, new features can be handled automagically.
1040        // typedef (unknown) config_list_t;
1041        // const config_list_t& get_transport_configuration();
1042        // int set_transport_configuration(...);
1043        // const config_list_t& get_audio_configuration()
1044        // ...etc.
1045
1046        /// Misc.
1047        int get_state();  // For implementing a global "pause", maybe
1048        /// playlist stuff?
1049        void panic();     // Immediately cease all audio
1050
1051private:
1052        static Hydrogen* __instance;
1053
1054        class HydrogenPrivate;
1055        HydrogenPrivate* d;
1056
1057        Hydrogen();  // private because Hydrogen is a singleton.
1058};
1059
1060
Note: See TracBrowser for help on using the browser.