| 1 | NOTES ON REDESIGNING THE TRANSPORT IN HYDROGEN |
|---|
| 2 | ============================================== |
|---|
| 3 | |
|---|
| 4 | -------------------------------------------------------------------------------- |
|---|
| 5 | Date: Tue, 26 Aug 2008 12:42:47 -0500 |
|---|
| 6 | From: "Gabriel M. Beddingfield" <gabriel@teuton.org> |
|---|
| 7 | To: hydrogen-devel@lists.sourceforge.net |
|---|
| 8 | Subject: Re: [Hydrogen-devel] The transport, BBT, and Jack |
|---|
| 9 | |
|---|
| 10 | Jakob 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 | |
|---|
| 14 | Agreed. However, we may want to try and put a half-way decent band-aid on it |
|---|
| 15 | and 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 | |
|---|
| 20 | Actually... that's where the code already is, isn't it? And that's kind of the |
|---|
| 21 | problem. 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 |
|---|
| 23 | trying to cleverly update that info. It looks like the sequencer was designed |
|---|
| 24 | as a standalone sequencer (master), and later it was retrofitted with a slave |
|---|
| 25 | option. So what we have is that even when Hydrogen is a transport slave, it |
|---|
| 26 | *thinks* it's the master (in hydrogen.cpp). |
|---|
| 27 | |
|---|
| 28 | How 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 |
|---|
| 30 | current buffer. Then, create different Transport implementations depending on |
|---|
| 31 | the situations. |
|---|
| 32 | |
|---|
| 33 | SEQUENCER ABSTRACT INTERFACE IMPLEMENTATIONS BACKEND INTERFACES |
|---|
| 34 | ========= ================== =============== ==================== |
|---|
| 35 | |
|---|
| 36 | Hydrogen <--TransportInterface <--+-- MasterTransport --> JackTransportAdapter |
|---|
| 37 | | | | |
|---|
| 38 | | +-- SlaveJackTransport <-- jackd <-+ |
|---|
| 39 | V | |
|---|
| 40 | AudioOutput +-- SlaveMTCTransport <-- MidiDriver |
|---|
| 41 | | |
|---|
| 42 | +-- SlaveOtherTransport |
|---|
| 43 | | |
|---|
| 44 | +-- MasterTrickedOutExperimentalTransport |
|---|
| 45 | | |
|---|
| 46 | |
|---|
| 47 | class TransportInterface |
|---|
| 48 | { |
|---|
| 49 | public: |
|---|
| 50 | virtual ~TransportInterface() {} |
|---|
| 51 | |
|---|
| 52 | uint32_t getBufferOffsetFrameForTick(uint32_t tick) = 0; |
|---|
| 53 | // ... other methods ... |
|---|
| 54 | }; |
|---|
| 55 | |
|---|
| 56 | Then, H2Core::Hydrogen doesn't care about sample rates, frames-per-tick, |
|---|
| 57 | samples-per-beat, tempo, or anything. We just go through the Note queue and |
|---|
| 58 | say, "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 | |
|---|
| 63 | See above. Good suggestion. I've been thinking about doing this with InConcert |
|---|
| 64 | as 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 | |
|---|
| 71 | Maybe I'm misunderstanding what you're saying... but isn't that sort of a given |
|---|
| 72 | when using the transport? ...that your time signatures need to line up? |
|---|
| 73 | Gracefully 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 | |
|---|
| 80 | I thought I heard that in Song mode, too, recently. I was throwing a lot of |
|---|
| 81 | wacked out tempo changes, though. |
|---|
| 82 | |
|---|
| 83 | Peace, |
|---|
| 84 | Gabriel |
|---|
| 85 | |
|---|
| 86 | -------------------------------------------------------------------------------- |
|---|
| 87 | Date: Sun, 1 Mar 2009 03:55:59 +0100 |
|---|
| 88 | From: "m.wolkstein@gmx.de" <m.wolkstein@gmx.de> |
|---|
| 89 | To: hydrogen-devel <hydrogen-devel@lists.sourceforge.net> |
|---|
| 90 | Subject: Re: [Hydrogen-devel] Gabriel Beddingfield commited [851]: Fix |
|---|
| 91 | double-hit at start when JACK Transport Master. |
|---|
| 92 | |
|---|
| 93 | Am Sat, 28 Feb 2009 04:19:10 +0000 |
|---|
| 94 | schrieb 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 | |
|---|
| 108 | hiho, the old game :-(. |
|---|
| 109 | after commit 851 it's not possible to change tempo during play. |
|---|
| 110 | if you use old tabtempo (altgr + back slash) or new beatcounter (coma) |
|---|
| 111 | the transport position jumps around (during play). |
|---|
| 112 | so no live tempochange as master will be possible. that's definitely |
|---|
| 113 | not usable for this use. jack time master implementation based on this |
|---|
| 114 | feature. so other applications can follow h2 also during tempo change. |
|---|
| 115 | |
|---|
| 116 | maybe we need a small unitest for some important features. |
|---|
| 117 | |
|---|
| 118 | here a small list over things (this is what i think) that transport |
|---|
| 119 | have 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 | |
|---|
| 135 | wolke |
|---|
| 136 | |
|---|
| 137 | -------------------------------------------------------------------------------- |
|---|
| 138 | Date: Sun, 01 Mar 2009 21:16:37 -0600 |
|---|
| 139 | From: "Gabriel M. Beddingfield" <gabriel@teuton.org> |
|---|
| 140 | To: hydrogen-devel@lists.sourceforge.net |
|---|
| 141 | Subject: Re: [Hydrogen-devel] Gabriel Beddingfield commited [851]: Fix double-hit |
|---|
| 142 | at start when JACK Transport Master. |
|---|
| 143 | |
|---|
| 144 | This is a multi-part message in MIME format. |
|---|
| 145 | --------------000603030801070202020104 |
|---|
| 146 | Content-Type: text/plain; charset=us-ascii; format=flowed |
|---|
| 147 | Content-Transfer-Encoding: 7bit |
|---|
| 148 | |
|---|
| 149 | m.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 | |
|---|
| 155 | Actually, 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 | |
|---|
| 162 | Yes, 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 | |
|---|
| 170 | If everyone else agrees that our users only plan to use the H2 transport with |
|---|
| 171 | Ardour 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 | |
|---|
| 176 | No. This is wrong. |
|---|
| 177 | |
|---|
| 178 | There should be no buffersize correction. Frame=0 should be 1:1:0... not |
|---|
| 179 | Frame=getBufferSize(). Any sort of buffersize correction like this is *not* |
|---|
| 180 | conforming to the transport and is working around some other bug. |
|---|
| 181 | |
|---|
| 182 | I did some transport auditing, and find that Hydrogen (as transport master) |
|---|
| 183 | isn't working right at all -- independent of audio. I wrote a JACK Client that |
|---|
| 184 | just observes the jack_position_t that is being fed to all the JACK Clients (see |
|---|
| 185 | attached). Here's what we get with your patch: |
|---|
| 186 | |
|---|
| 187 | usecs=179033476580 fps=48000 frame=0 bpm=110.4 B:B:T=1:1:0 bbt_offset=0 |
|---|
| 188 | usecs=179033497885 fps=48000 frame=1024 bpm=110.4 B:B:T=1:1:0 bbt_offset=0 |
|---|
| 189 | usecs=179033519235 fps=48000 frame=2048 bpm=110.4 B:B:T=1:1:40 bbt_offset=0 |
|---|
| 190 | usecs=179033540554 fps=48000 frame=3072 bpm=110.4 B:B:T=1:1:48 bbt_offset=0 |
|---|
| 191 | usecs=179033561939 fps=48000 frame=4096 bpm=110.4 B:B:T=1:1:56 bbt_offset=0 |
|---|
| 192 | |
|---|
| 193 | Notice that ticks 0 and 1024 are both 1:1:0, and that 2048 jumps to 1:1:40. I |
|---|
| 194 | would expect the ticks to go 0, 8, 16, etc. |
|---|
| 195 | |
|---|
| 196 | Here's rev 858: |
|---|
| 197 | |
|---|
| 198 | usecs=179454108209 fps=48000 frame=0 bpm=120 B:B:T=1:1:0 bbt_offset=0 |
|---|
| 199 | usecs=179454129526 fps=48000 frame=1024 bpm=120 B:B:T=1:1:0 bbt_offset=0 |
|---|
| 200 | usecs=179454150858 fps=48000 frame=2048 bpm=120 B:B:T=1:1:52 bbt_offset=0 |
|---|
| 201 | usecs=179454172379 fps=48000 frame=3072 bpm=120 B:B:T=1:1:60 bbt_offset=0 |
|---|
| 202 | usecs=179454193586 fps=48000 frame=4096 bpm=120 B:B:T=1:1:68 bbt_offset=0 |
|---|
| 203 | |
|---|
| 204 | ...not much better. |
|---|
| 205 | |
|---|
| 206 | What's more... the tick count is usually 8 ticks per period. But, with your |
|---|
| 207 | patch we sometimes get a period with only 4 ticks (1:2:76 -> 1:2:80): |
|---|
| 208 | |
|---|
| 209 | usecs=179034095196 fps=48000 frame=29696 bpm=110.4 B:B:T=1:2:52 bbt_offset=0 |
|---|
| 210 | usecs=179034118910 fps=48000 frame=30720 bpm=110.4 B:B:T=1:2:60 bbt_offset=0 |
|---|
| 211 | usecs=179034138749 fps=48000 frame=31744 bpm=110.4 B:B:T=1:2:68 bbt_offset=0 |
|---|
| 212 | usecs=179034160514 fps=48000 frame=32768 bpm=110.4 B:B:T=1:2:76 bbt_offset=0 |
|---|
| 213 | usecs=179034181174 fps=48000 frame=33792 bpm=110.4 B:B:T=1:2:80 bbt_offset=0 |
|---|
| 214 | usecs=179034201904 fps=48000 frame=34816 bpm=110.4 B:B:T=1:2:88 bbt_offset=0 |
|---|
| 215 | usecs=179034223196 fps=48000 frame=35840 bpm=110.4 B:B:T=1:2:96 bbt_offset=0 |
|---|
| 216 | |
|---|
| 217 | This happens regularly. Some other patches may do this, but I didn't see it |
|---|
| 218 | with rev 858. |
|---|
| 219 | |
|---|
| 220 | In contrast, I get this from InConcert: |
|---|
| 221 | |
|---|
| 222 | usecs=180994287332 fps=48000 frame=0 bpm=120 B:B:T=1:1:0 bbt_offset=0 |
|---|
| 223 | usecs=180994308692 fps=48000 frame=1024 bpm=120 B:B:T=1:1:15 bbt_offset=23 |
|---|
| 224 | usecs=180994330006 fps=48000 frame=2048 bpm=120 B:B:T=1:1:30 bbt_offset=47 |
|---|
| 225 | usecs=180994351333 fps=48000 frame=3072 bpm=120 B:B:T=1:1:46 bbt_offset=5 |
|---|
| 226 | usecs=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 |
|---|
| 229 | get the point.) |
|---|
| 230 | |
|---|
| 231 | *sigh* I'm not sure what to do. :-/ |
|---|
| 232 | |
|---|
| 233 | I'm backing out rev 851 to find a better solution. |
|---|
| 234 | |
|---|
| 235 | Peace, |
|---|
| 236 | Gabriel |
|---|
| 237 | |
|---|
| 238 | |
|---|
| 239 | |
|---|
| 240 | |
|---|
| 241 | |
|---|
| 242 | --------------000603030801070202020104 |
|---|
| 243 | Content-Type: text/x-c++src; |
|---|
| 244 | name="t_log_xport.cpp" |
|---|
| 245 | Content-Transfer-Encoding: 7bit |
|---|
| 246 | Content-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 | |
|---|
| 291 | using namespace std; |
|---|
| 292 | |
|---|
| 293 | struct 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 | |
|---|
| 302 | jack_client_t *_client; |
|---|
| 303 | const long int BUFSIZE = 262144; // max recording space. |
|---|
| 304 | xpos_t buf[BUFSIZE]; |
|---|
| 305 | unsigned long buf_pos = 0; |
|---|
| 306 | bool done = false; |
|---|
| 307 | |
|---|
| 308 | int 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 | |
|---|
| 338 | ostream& 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 | |
|---|
| 349 | int 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 | -------------------------------------------------------------------------------- |
|---|
| 385 | Date: Mon, 02 Mar 2009 13:03:48 -0600 |
|---|
| 386 | From: "Gabriel M. Beddingfield" <gabriel@teuton.org> |
|---|
| 387 | To: hydrogen-devel@lists.sourceforge.net |
|---|
| 388 | Subject: Re: [Hydrogen-devel] Gabriel Beddingfield commited [851]: Fix double-hit |
|---|
| 389 | at start when JACK Transport Master. |
|---|
| 390 | |
|---|
| 391 | m.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 | |
|---|
| 402 | Sorry, I wrote that in a somewhat intimidating manner, didn't I? What I mean is |
|---|
| 403 | this: if we all agree to *not* support Ardour's bug, then I'm OK with taking it |
|---|
| 404 | out. |
|---|
| 405 | |
|---|
| 406 | However, 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 | |
|---|
| 420 | The problem is that adding or subtracting any offset violates the principles of |
|---|
| 421 | using the transport in the first place. The only Right reason for doing it is |
|---|
| 422 | for latency compensation. There should be ZERO latency between H2 and Ardour. |
|---|
| 423 | Transport frame 0 in H2 is transport frame 0 in Ardour. If not -- somone is |
|---|
| 424 | cheating. |
|---|
| 425 | |
|---|
| 426 | The buffer offset says that frame 1024 in Ardour is frame 0 in H2. This is |
|---|
| 427 | wrong. (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 | |
|---|
| 432 | It "records" the transport state whenever it is rolling. It doesn't care about |
|---|
| 433 | tempo changes or anything. When someone presses PLAY, it records. When someone |
|---|
| 434 | pressed 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 | |
|---|
| 443 | Samplerate has nothing to do with it. You probably know that whenever JACK does |
|---|
| 444 | the process() callback, all we care about is the next nFrames samples. We don't |
|---|
| 445 | care about the oscillators in the hardware, or what the time on the wall clock |
|---|
| 446 | says... we just care about reading or writing the next nFrames samples. The |
|---|
| 447 | only reason why we care about the sample rate is so that we can resample our |
|---|
| 448 | waveforms. |
|---|
| 449 | |
|---|
| 450 | Usecs 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 |
|---|
| 452 | because 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 | |
|---|
| 467 | No, this is handled by the transport controls. Whoever calls |
|---|
| 468 | jack_transport_locate() decides what the start frame is. Hydrogen and Ardour |
|---|
| 469 | are supposed to play whatever happens at frame # jack_position_t.frame. (In |
|---|
| 470 | other words, H2 and Ardour should have perfectly synchronized frames: 0, 1024, |
|---|
| 471 | 2048, 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 | |
|---|
| 484 | Did you know: There is *no* callback whenever the transport master CHANGES? If |
|---|
| 485 | H2 is the transport master... but Ardour takes over -- there is *no* |
|---|
| 486 | notification 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 |
|---|
| 488 | based on who's controlling the transport. (I discovered this in the last few days.) |
|---|
| 489 | |
|---|
| 490 | We're not supposed to care who the transport master is. We're not supposed to |
|---|
| 491 | make 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 | |
|---|
| 509 | Hydrogen was the transport master for this test. The BBT given is whatever |
|---|
| 510 | Hydrogen wrote to the jack_position_t struct. Hydrogen wrote bpm, bar, beat, |
|---|
| 511 | and tick. (H2 doesn't supply bbt_offset, so it's assumed 0, per the Jack docs.) |
|---|
| 512 | |
|---|
| 513 | One 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 | |
|---|
| 519 | usecs is supplied by the JACK server. |
|---|
| 520 | |
|---|
| 521 | >> Peace, |
|---|
| 522 | >> Gabriel |
|---|
| 523 | > |
|---|
| 524 | > also peace wolke :-) |
|---|
| 525 | |
|---|
| 526 | :-) |
|---|
| 527 | |
|---|
| 528 | -gabriel |
|---|
| 529 | |
|---|
| 530 | -------------------------------------------------------------------------------- |
|---|
| 531 | 2009-03-03 TRANSPORT DESIGN CONCEPTS |
|---|
| 532 | ==================================== |
|---|
| 533 | |
|---|
| 534 | HydrogenGUI |
|---|
| 535 | A |
|---|
| 536 | | Current Song |
|---|
| 537 | V |(**) |
|---|
| 538 | TransportControlInterface 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 | |
|---|
| 546 | TransportPosition (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 | |
|---|
| 559 | When H2 uses the Jack Transport, it will *always* slave to the |
|---|
| 560 | JackTransportMaster. When H2 is the JACK transport master, then |
|---|
| 561 | InternalTransportMaster will be used for the external transport's |
|---|
| 562 | callback. However, we'll still slave to JackTransportMaster (whether |
|---|
| 563 | we think we're in control or not). |
|---|
| 564 | |
|---|
| 565 | The intention when slaving to the JACK transport is that we listen to |
|---|
| 566 | the BBT coming from the transport (only) -- if it is provided. If it |
|---|
| 567 | is *not* provided, then we need some manner of fallback. |
|---|
| 568 | |
|---|
| 569 | InternalTransportMaster is just a concept for now. The plan is to |
|---|
| 570 | start off with a simple implementation (i.e. no tap-tempo). Once |
|---|
| 571 | working, we can provide some tap-tempo models. |
|---|
| 572 | |
|---|
| 573 | Also, by modularizing these, it's much easier to write unit tests to |
|---|
| 574 | ensure that the transports will follow certain rules. |
|---|
| 575 | |
|---|
| 576 | The names "Master" are a little confusing when you think in terms of being the |
|---|
| 577 | Jack Transport Master. |
|---|
| 578 | |
|---|
| 579 | -------------------------------------------------------------------------------- |
|---|
| 580 | 2009-03-05 SEQUENCER AND SAMPLER |
|---|
| 581 | ================================ |
|---|
| 582 | |
|---|
| 583 | ROLES..... |
|---|
| 584 | |
|---|
| 585 | The sequencer (H2Core::Hydrogen) looks at the timeline (Transport) and the song |
|---|
| 586 | and schedules sounds to be made by the synthesizer (Sampler). The sampler is |
|---|
| 587 | responsible for organizing and maintaining the current drum kit. When |
|---|
| 588 | controlling the sampler, it should not need any information about bars, beats, |
|---|
| 589 | ticks, or the song. Instead, the sequencer schedules samples to be triggered at |
|---|
| 590 | specific frame numbers. |
|---|
| 591 | |
|---|
| 592 | MUTING..... |
|---|
| 593 | |
|---|
| 594 | So then, who is responsible for *MUTING*? It could go either way. If an |
|---|
| 595 | instrument is muted, the sampler could ignore any triggers for that instrument. |
|---|
| 596 | On the other hand, if the instrument is muted then the sequencer could *not* |
|---|
| 597 | schedule the trigger. |
|---|
| 598 | |
|---|
| 599 | If muting is handled by the sampler, it's possible that muted channels can still |
|---|
| 600 | have their signals registered on the meters. All the normal processing is done, |
|---|
| 601 | but the final signal is not mixed. Or, if no processing is to be done... that |
|---|
| 602 | can still be done efficiently be the sampler. |
|---|
| 603 | |
|---|
| 604 | On the other hand, if muting is handled by the sequencer then scheduling notes |
|---|
| 605 | for the sampler is nearly identical to scheduling notes for MIDI output. |
|---|
| 606 | |
|---|
| 607 | CURRENTLY, Hydrogen does not show meters on a muted channel. |
|---|
| 608 | |
|---|
| 609 | THEREFORE: Muting will be handled by the sequencer. |
|---|
| 610 | |
|---|
| 611 | AUDIO DRIVERS....... |
|---|
| 612 | |
|---|
| 613 | What, then, should be the relationship between the sampler and the audio |
|---|
| 614 | driver(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. |
|---|
| 616 | So, 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 | |
|---|
| 636 | I would be inclined to say that the Sequencer is having way too much to do with |
|---|
| 637 | the audio... but someobody's got to be coordinating things. However, it might |
|---|
| 638 | make more sense (and reduce buffers) if the Sampler was handling things with the |
|---|
| 639 | audio driver. This would mean that the effects, too, would fall under the |
|---|
| 640 | domain of the Sampler. |
|---|
| 641 | |
|---|
| 642 | It really comes down to what is more flexable... but keeps the parts separate. |
|---|
| 643 | |
|---|
| 644 | IMHO, the sampler is our synthesizer... and needs to have a close |
|---|
| 645 | relationship with the audio driver. |
|---|
| 646 | |
|---|
| 647 | THEREFORE: FX and audio outs will be handled by the Sampler. |
|---|
| 648 | |
|---|
| 649 | NOTE OFF EVENTS.......... |
|---|
| 650 | |
|---|
| 651 | There's been two other devs working on handling note off stuff (including |
|---|
| 652 | Michael Wolkstein). These should also be scheduled by the sequencer. How they |
|---|
| 653 | are handled by the sampler is instrument-dependent. |
|---|
| 654 | |
|---|
| 655 | Since 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 |
|---|
| 657 | recall what the MIDI standard says about repeat notes... is it OK to send |
|---|
| 658 | multiple note-on's but only one note-off? How do we suppress the first |
|---|
| 659 | note-off? |
|---|
| 660 | |
|---|
| 661 | Answer: MIDI assumes that when a note is on, it's on... and off is |
|---|
| 662 | off. It doesn't consider having the same note on twice at the same |
|---|
| 663 | time (unison). Therefore, there's no requirements about NOTE ON's |
|---|
| 664 | being preceeded by NOTE OFF's. Likewise, a NOTE OFF can be sent at |
|---|
| 665 | any time, and will have no effect if the note is already off. |
|---|
| 666 | So... when the sequencer schedules a note to re-trigger... it needs to |
|---|
| 667 | cancel any other NOTE OFF events that have been scheduled for that |
|---|
| 668 | not. |
|---|
| 669 | |
|---|
| 670 | There 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 | |
|---|
| 676 | SAMPLER EVENT SEQUENCE...... |
|---|
| 677 | |
|---|
| 678 | To communicate events to the sampler, the sequencer will create a script (event |
|---|
| 679 | list) that is shared with the sampler. This event list is essentially |
|---|
| 680 | translating the song B:b.t into frame numbers. The list will persist between |
|---|
| 681 | process() cycles... and should probably be some manner of ring buffer. |
|---|
| 682 | |
|---|
| 683 | In this script, frame 0 will refer to the first frame of the current process() |
|---|
| 684 | cycle. When the sequencer is scheduling... if a song tick could overlap into |
|---|
| 685 | the current process() cycle (through lead/lag/humanize)... then the sequencer |
|---|
| 686 | may schedule that tick. This means that THE SEQUENCER CAN SCHEDULE MORE THAN |
|---|
| 687 | ONE PERIOD'S WORTH OF EVENTS. How many periods it could need to schedule can be |
|---|
| 688 | determined based on the max size of lead, lag, and humanize. |
|---|
| 689 | |
|---|
| 690 | Because frames will be scheduled relative to the current process() cycle, this |
|---|
| 691 | means that frame numbers have to be re-normalized after every cycle. Otherwise, |
|---|
| 692 | some other scheme would need to be implemented. (e.g. frames scheduled relative |
|---|
| 693 | to some number that may or may not correspond to the real transport frame.) |
|---|
| 694 | |
|---|
| 695 | Since there may be many ways to optimize this scheme... I wonder if it would be |
|---|
| 696 | worth giving it some manner of container sematics. Then, we can change the |
|---|
| 697 | implementation without having to mess with the sequencer or the sampler. But, |
|---|
| 698 | this 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 | |
|---|
| 707 | SORTING......... |
|---|
| 708 | |
|---|
| 709 | Seems that someone needs to sort the events in the event sequence. Right now, |
|---|
| 710 | it's delegated to a priority_queue... but I think that actually requires memory |
|---|
| 711 | allocation in realtime sections. |
|---|
| 712 | |
|---|
| 713 | WITHOUT sorting, it will require several passes over the audio buffers in the |
|---|
| 714 | sequencer... and maybe several passes over the event sequence. This is |
|---|
| 715 | essentially sorting. |
|---|
| 716 | |
|---|
| 717 | WITH sorting... it can be done in any of these places: the sequencer, the event |
|---|
| 718 | list (e.g. a priority queue), or the sampler. |
|---|
| 719 | |
|---|
| 720 | I would prefer that the event list sort itself... or... |
|---|
| 721 | |
|---|
| 722 | Or perhaps have the sequencer just dump all the events and sort them at one time |
|---|
| 723 | using 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 | |
|---|
| 732 | TRANSPORT POSITION..... |
|---|
| 733 | |
|---|
| 734 | I've already written a bunch of code using ticks_per_beat and |
|---|
| 735 | beats_per_bar as floating point types. Need to decide now if these |
|---|
| 736 | will become integers. |
|---|
| 737 | |
|---|
| 738 | Calculation efficiency is not much different between FDIV, DIV, and |
|---|
| 739 | IDIV instructions on an x86 (DIV is a little faster). So, it ends up |
|---|
| 740 | being an issue of clarity and flexibility. If we snap these to be |
|---|
| 741 | integers... we'll need to negotiate with JACK about when they're *not* |
|---|
| 742 | integers. If we don't snap them to integers, we need to handle them |
|---|
| 743 | *without* the assumption that they *do* snap to integers. |
|---|
| 744 | |
|---|
| 745 | Here'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 | |
|---|
| 769 | These are close analog's to JACK's jack_position_t. Paul Davis |
|---|
| 770 | explained it like this: |
|---|
| 771 | |
|---|
| 772 | -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~ |
|---|
| 773 | Date: Wed, 4 Mar 2009 07:46:03 -0500 |
|---|
| 774 | From: Paul Davis <paul@linuxaudiosystems.com> |
|---|
| 775 | To: "Gabriel M. Beddingfield" <gabriel@teuton.org> |
|---|
| 776 | Cc: JACK <jack-devel@lists.jackaudio.org> |
|---|
| 777 | Subject: Re: [Jack-Devel] jack_transport_reposition() and the JackPositionBBT |
|---|
| 778 | fields |
|---|
| 779 | |
|---|
| 780 | On 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 | |
|---|
| 790 | there are musical traditions around the world in which beats_per_bar and the |
|---|
| 791 | subdivisions of a bar into beats cannot be properly represented with |
|---|
| 792 | integers. indian and some south-east asian traditions (bali and thailand) |
|---|
| 793 | contain music in which it makes sense to think of a meter as containing half |
|---|
| 794 | beats, for example. it doesn't work to just double the tempo and thus move |
|---|
| 795 | to a whole number of beats - this misses the subtlety of the shifting |
|---|
| 796 | relationship between the rhythmic and other components of the music. |
|---|
| 797 | |
|---|
| 798 | ticks per beat is normally a mid-size integer value that represents "BBT |
|---|
| 799 | resolution". it is typically a number with a large number of factors, which |
|---|
| 800 | thus allows 1 beat to be divided in many different ways and still end up |
|---|
| 801 | with an integral number of ticks. ardour, for example, uses 1960 ticks per |
|---|
| 802 | beat, which has a very large set of numbers as factors. this means that you |
|---|
| 803 | can divide a beat into, say, 8ths, 10th, 12ths and so on, and each division |
|---|
| 804 | is an exact number of ticks. all MIDI sequencers do this, since its a way to |
|---|
| 805 | avoid rounding errors. |
|---|
| 806 | |
|---|
| 807 | even though this number is always going to be an integer, to harmonize with |
|---|
| 808 | the floating point values of beats per bar and beat type, it was defined as |
|---|
| 809 | a floating point value. |
|---|
| 810 | |
|---|
| 811 | -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~ |
|---|
| 812 | |
|---|
| 813 | Now, 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 | |
|---|
| 825 | I think beats_per_bar and beats_per_minute remain floating points. |
|---|
| 826 | |
|---|
| 827 | If we allow beat_type to be floating point, then (as Paul said), |
|---|
| 828 | ticks_per_beat would need to be a floating point to match. Suppose |
|---|
| 829 | the time signature is something really random like 6.666/3.5, with |
|---|
| 830 | 1680.0 ticks per beat. Every measure would have: |
|---|
| 831 | |
|---|
| 832 | 6.666 * 1680.0 = 11198.88 ticks/bar |
|---|
| 833 | |
|---|
| 834 | If we calculate bar start ticks by rounding to 11199 |
|---|
| 835 | ticks/bar... roundoff error will catch up to us. |
|---|
| 836 | |
|---|
| 837 | HOWEVER, 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 | -------------------------------------------------------------------------------- |
|---|
| 843 | 2009-03-16 SEQUENCER THRU AUDIO OUT |
|---|
| 844 | =================================== |
|---|
| 845 | |
|---|
| 846 | Sequencer |
|---|
| 847 | | | |
|---|
| 848 | | +----> (Other outputs) |
|---|
| 849 | | |
|---|
| 850 | | (Via SeqScript) |
|---|
| 851 | | |
|---|
| 852 | V |
|---|
| 853 | Sampler |
|---|
| 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 | |
|---|
| 876 | This causes the mixer to have a ton of buffers. But, with "tracking outs," I |
|---|
| 877 | don't think there's much way to avoid it. |
|---|
| 878 | |
|---|
| 879 | One way to reduce the buffers is to provide an interface for the instruments to |
|---|
| 880 | write directly to mixer buffers with desired gains. |
|---|
| 881 | |
|---|
| 882 | int 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 | |
|---|
| 899 | But... things change for tracking outs. |
|---|
| 900 | |
|---|
| 901 | -- or do they? We still ask the mixer for a pointer to the buffer where the |
|---|
| 902 | data goes. If the mixer wants direct outs, the atten will be something like |
|---|
| 903 | 1/32. If the mixer handles tracking outs, it will be 1.0. |
|---|
| 904 | |
|---|
| 905 | But... this setup feels like we're wagging the dog. The call stack is something |
|---|
| 906 | like: |
|---|
| 907 | |
|---|
| 908 | jackd process() |
|---|
| 909 | Hydrogen::process() |
|---|
| 910 | Sequencer::process() |
|---|
| 911 | Sampler::process() |
|---|
| 912 | Instrument::process() |
|---|
| 913 | Mixer::get_buffer() |
|---|
| 914 | |
|---|
| 915 | Seems like the Mixer should be in control of the instruments, instead of the |
|---|
| 916 | Instruments being in control. But how do we do that w/o the mixer being part of |
|---|
| 917 | the sampler (thus, not a generic mixer)? |
|---|
| 918 | |
|---|
| 919 | But -- it doesn't seem like it would be too difficult to optimize tracks like |
|---|
| 920 | this -- with inst's that aren't playing assumed to be zero and skipped. |
|---|
| 921 | |
|---|
| 922 | Here's a target Mixer API: |
|---|
| 923 | |
|---|
| 924 | int 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 | |
|---|
| 938 | int 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 | |
|---|
| 952 | class Mixer |
|---|
| 953 | { |
|---|
| 954 | //... |
|---|
| 955 | |
|---|
| 956 | private: |
|---|
| 957 | some_sequence_type<SeqClient*> used; |
|---|
| 958 | some_map_type<SeqClient* sample_type*> bufs; |
|---|
| 959 | |
|---|
| 960 | //... |
|---|
| 961 | }; |
|---|
| 962 | |
|---|
| 963 | sample_type* Mixer::get_buf_for_input(that) |
|---|
| 964 | { |
|---|
| 965 | used.push_back(that); |
|---|
| 966 | return bufs[that]; |
|---|
| 967 | } |
|---|
| 968 | |
|---|
| 969 | int 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 | } |
|---|