Hi Jakob,

On Thu, 23 Jul 2009, Jakob Lund wrote:

> transport_redesign_2 branch. This is really cool! I like the idea of

Thanks!

> I'm not so sure about the SeqScript idea though, it seems that every note
> (that has length set) gets two events associated with it instead of one,
> which could complicate things. Why not just use Note objects as before? the
> sampler still uses them internally, and in that process, various fields on
> the Note get manipulated (notably m_fSamplePosition).

Good point.  That's something I'm not terribly happy about with SeqScript. 
The way that Note gets wrapped inside of SeqEvent is a little clunky.

Here's the ideas that went in to what we have today (...for right or 
wrong...):

   + Make Note very lightweight.  Currently Note carries
     a QString (inherited from Object), all the ADSR
     state parameters (which should be handled by the
     voice making the noise), some filter paramters,
     etc., etc.  Make it something easy and fast
     to copy.

   + Remove the notion of position from the Note
     itself.  Conceptually, a note is a pitch
     (instrument), length, velocity, etc.  The position
     of the note in the song is information _about_
     the note, but is not part of the note itself.

   + Things like m_fSamplePosition shouldn't be in
     Note (conceptually).  It is a book-keeping,
     implementation detail of the instrument.  (Info.
     _about_ the note... i.e. how long we've been
     playing this here note.)

   + Create an intermediate language to communicate
     Note events.  (In retrospect, this is similar
     to MIDI.)  This de-couples inputs and outputs.
     The scripting language (thus "SeqScript") would
     be something like this:

          time=0 Note On, Inst #3, Vel=90
          time=0 Note On, Inst #2, Vel=20
          time=24 Note Off, Inst #3, Vel=x
          time=36 Note On, Inst #1, Vel=119
          ...

     The times are relative to the frame in the
     current process cycle.  Now the Sampler doesn't
     care about note position or ticks or anything.
     It just plays Note at frame Z.

   + The container that holds all the events in
     SeqScript should be a pre-allocated, fixed
     block of memory.

FWIW, The now-abandoned 'transport_redesign' was closer to these goals... 
but further from release.  :-)


> A full implementation of the SongSequencer would also have to keep some 
> copied notes around for lookahead purposes (I imagine setting a floating 
> point tick position on them first [to include humanize etc.], putting 
> them in a priority queue based on that tick position, and then only 
> converting to frame positions for notes that will actually _play_ during 
> the current buffer cycle.) It seems to me as the SeqScript just adds 
> anothe round of copying Note objects (I might very well be wrong here) ?

Short version:  SongSequencer would go ahead and schedule the notes in 
SeqScript, even though they are beyond the current process() cycle. 
SeqScript keeps those notes, but when sending to Outputs... only feeds the 
stuff for the current cycle.  (See SeqScript::consumed(), 
and SeqScript::end_const(frame_type)).

When scheduling notes, SongSequencer has to look ahead an appropriate 
amount and schedule all those events (rendering any randomization values 
now).  So, there will probably be a cursor where SongSequencer says to 
itself, "I've already scheduled up to.... here."

SongSequencer then schedules all the notes in its look-ahead time-frame. 
It schedules _all_ the notes, scheduling by their offset from the current 
process() cycle.  It schedules them even if they are for the _next_ 
process() cycle (frame > nframes).

SeqScript handles all the notes that SongSequencer gives it.  Keeping 
track of the frame number.

When the Sampler interfaces with SeqScript, it will _only_ see the events 
for the current process() cycle.  (From SeqScript::begin_const() to 
SeqScript::end_const(nFrames).)

At the end of the process() cycle, SeqScript::consumed(nFrames) is called. 
This signals SeqScript to "forget" all the notes from 0 up to nFrames, and 
adjust the frame offset values for all notes currently scheduled.

> Another question is the ring buffer in SeqScript -- `ring buffer` implies
> `first in-first out`, and this becomes a problem as soon as lead/lag,
> humanize time etc. get implemented. I was imagining something like a free
> list, making it possible to delete note objects in random order, without
> using `delete`.

I think I originally intended to use a ring buffer, but what I really 
wanted was just a pre-allocated, reusable, block of memory.  So, I'm not 
using a ring buffer.

SeqScriptPrivate (the implementation of SeqScript) has two linked 
lists, where all the objects are actually in a vector.  Here's the 
relationship:

    internal_sequence_type:  pre-allocated block of memory (vector)
    internal_iterator:       linked list of free memory
    iterator:                linked list of note events.

Notes are inserted and stored in the linked-list in frame-order (so it is 
a sorted list).  FWIW, which is the note list and which is the free-memory
list, and which iterators go to the vector or the lists... it's confusing 
and hard to follow.  However, ATM it works.

> Last question: Why the technique of using another class SomethingPrivate 
> for class Something's private stuff? What is the reason for not just 
> declaring the methods and variables private inside the class itself?

Several reasons...

   + There is a desire to spin off libhydrogen to make, for
     instance, DSSI plugins, a command-line hydrogen, etc.

   + Hydrogen takes forever to compile.  audio_engine.h includes
     EVERYTHING, and is included in EVERY GUI file.

   + There's not good separation of parts between different
     parts of Hydrogen.  The objects in libhydrogen are a
     tangled web of wierd dependencies.  When merging jack_zombies,
     I found that Song depends on Hydrogen being instanced (??),
     that class Hydrogen instances Playlist when initializing.
     Playlist depends on HydrogenApp.  So, the audio classes
     now depend on the GUI classes.  I find stuff like this
     all the time.

   + Suppose the GUI thread wants to use the Transport.  Meanwhile,
     Hydrogen's internal transport needs some private function foo()
     that allows it to do some internal thing.  Now, all the GUI
     code has to recompile because you changed the header.

So, I'm trying to make judicious use of the d-pointer method 
(used extensively in Qt).

For example, the GUI code needs to be able to add notes to SeqScript 
because we can have note events that come from the GUI, right? 
Meanwhile, SeqScriptPrivate needs work.  Maybe even a total redesign.  You 
can totally rewrite SeqScriptPrivate, and in the end you will only have to 
recompile SeqScriptPrivate.cpp and SeqScript.cpp.  If we had done it all 
in SeqScript, we would start a full recompile of hydrogen and go get some 
more coffee.

Take this a step further... suppose we spin off libhydrogen.  Someone came 
up with some plugin or application that allows Hydrogen to play notes from 
incoming OSC events.  He also needs to manipulate SeqScript.  Since we did 
the d-pointer, he doesn't even need to recompile his plugin.  Just update 
the library, and it works.

So, I'm trying to totally separate the GUI code from the audio 
implementation.  To do this I'm creating interfaces and using d-pointers. 
I'm trying to only use it where it makes sense.  (E.g. using a d-pointer 
for Note would be overkill.)

Thanks,
Gabriel


------------------------------------------------------------------------------
_______________________________________________
Hydrogen-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/hydrogen-devel

Reply via email to