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