From: xbunny
Subject: set like table
Date: 
Message-ID: <7iNzf.158314$D47.142717@fe3.news.blueyonder.co.uk>
Im very new to Lisp and was wondering if someone could help me out with 
the most correct way to do the following.

In my midi sequencer I define phrases of notes using lists of the 
following structure:

(defstruct note
   (start-time 0 :type fixnum)
   (duration 0 :type fixnum)
   (channel 0 :type fixnum)
   (note 0 :type fixnum)
   (attack-velocity 0 :type fixnum)
   (release-velocity 0 :type fixnum))

What I need to do is mix many phrases into one continuous stream of midi 
events. These things:

;; intermediate structure stored in the hash table of notes
;; ready for packing into a midi buffer
(defstruct short-midi
   (channel :byte)
   (event :byte)
   (velocity :byte)
   (note :byte))

What I do is use a hash table with each hash table key being the note 
event time and the value being the list of events that happen at that 
time. Thus I have this which places the phrase into the hash table... 
(please excuse if mozilla wraps the source)

(defun note-list-to-hash (list hash)
   "add a list of 'notes' to the hash table"
   (loop for n in list do
	(let ((note-on (make-short-midi :channel (note-channel n)
					:event *midi-note-on*
					:velocity (note-attack-velocity n)
					:note (note-note n)))
	      (note-off (make-short-midi :channel (note-channel n)
					 :event *midi-note-off*
					 :velocity (note-release-velocity n)
					 :note (note-note n))))
	  (push note-on (gethash (note-start-time n) hash))
	  (push note-off (gethash (+ (note-start-time n) (note-duration n)) 
hash)))))

...and this which converts the hash table into the stream of packed 
events... (pack-notes just bit shifts the structure values into a value 
for the midi output routines)

(defun note-hash-to-midi-buffer (hash)
   "convert a hash table of short-midis to a continuous buffer of packed 
short-midi dwords"
   (let ((buffer)
	(time 0))
     (maphash #'(lambda (key val)
		 (let ((diff (- key time))
		       (first t))
		   ; because we use push to populate the value list in the hash table
		   ; we reverse the list when putting it into the buffer
		   ; so the note events are in the correct order
		   (loop for n in (reverse val) do
			 (if first
			     (push diff buffer)
			     (push 0 buffer))
			 (setf first nil)
			 (push 0 buffer)
			 (push (pack-note (short-midi-channel n)
					  (short-midi-event n)
					  (short-midi-note n)
					  (short-midi-velocity n)) buffer)))
		 (setf time key))
	     hash)
     (nreverse buffer)))

The problem I see with this is that I rely on the entries in the hash 
table being sorted and I dont think this behaviour is garanteed. In the 
original c++ I used a std::set and its something with thats 
functionality that I require. Overall I get the feeling that what I am 
trying to acheive can be done much tidily so any advice would be greatly 
appreciated.

Best regards, Bunny

From: Wade Humeniuk
Subject: Re: set like table
Date: 
Message-ID: <8rPzf.97407$6K2.5992@edtnps90>
xbunny wrote:

> 
> The problem I see with this is that I rely on the entries in the hash 
> table being sorted and I dont think this behaviour is garanteed. In the 
> original c++ I used a std::set and its something with thats 
> functionality that I require. Overall I get the feeling that what I am 
> trying to acheive can be done much tidily so any advice would be greatly 
> appreciated.
> 

Here is a solution that I think works.  It sorts the keys in the hash table
then uses that to access the notes in order.

(defun note-hash-to-midi-buffer (hash)
   "convert a hash table of short-midis to a continuous buffer of packed short-midi dwords"
   (let ((buffer)
         (time 0)
         (keys (sort (loop for key being the hash-key of hash
                           collect key)
                     #'<))) ;;sort keys by time
     (map nil (lambda (key)
                (let ((diff (- key time))
                      (first t)
                      (val (gethash key hash)))
            ; because we use push to populate the value list in the hash table
            ; we reverse the list when putting it into the buffer
            ; so the note events are in the correct order
                  (loop for n in (reverse val) do
                        (if first
                            (push diff buffer)
                          (push 0 buffer))
                        (setf first nil)
                        (push 0 buffer)
                        (push (pack-note (short-midi-channel n)
                                         (short-midi-event n)
                                         (short-midi-note n)
                                         (short-midi-velocity n)) buffer)))
                (setf time key))
          keys)
     (nreverse buffer)))

Wade
From: Wade Humeniuk
Subject: Re: set like table
Date: 
Message-ID: <xEPzf.76602$m05.41056@clgrps12>
I cleaned up the loop a bit

(defun note-hash-to-midi-buffer (hash)
   "convert a hash table of short-midis to a continuous buffer of packed short-midi dwords"
   (let ((buffer)
         (time 0)
         (keys (sort (loop for key being the hash-key of hash
                           collect key)
                     #'<))) ;;sort keys by time
     (map nil (lambda (key)
            ; because we use push to populate the value list in the hash table
            ; we reverse the list when putting it into the buffer
            ; so the note events are in the correct order
                (loop with diff = (- key time)
                      for n in (reverse (gethash key hash))
                      for first = t then nil
                      do
                      (push (if first diff 0) buffer)
                      (push 0 buffer)
                      (push (pack-note (short-midi-channel n)
                                       (short-midi-event n)
                                       (short-midi-note n)
                                       (short-midi-velocity n)) buffer))
                (setf time key))
          keys)
     (nreverse buffer)))

Wade
From: xbunny
Subject: Re: set like table
Date: 
Message-ID: <G7Rzf.159203$D47.145690@fe3.news.blueyonder.co.uk>
Wade Humeniuk wrote:

> I cleaned up the loop a bit

thanks wade, I have been debating in my head whether the hash table is 
the best data structure to use here. The other opion I found was an 
associative list but I dont think it offers me any advantages here. Is 
that right or have I over looked something?

Cheers Bunny
From: Wade Humeniuk
Subject: Re: set like table
Date: 
Message-ID: <J9Tzf.110808$km.80968@edtnps89>
xbunny wrote:
> Wade Humeniuk wrote:
> 
>> I cleaned up the loop a bit
> 
> thanks wade, I have been debating in my head whether the hash table is 
> the best data structure to use here. The other opion I found was an 
> associative list but I dont think it offers me any advantages here. Is 
> that right or have I over looked something?

I hardly know anything about midi, but from the looks of your data
its a compacted time ordered sequence of notes.  Since music is like a sequence
of ordered notes (like 1/2 1/4 1/8 etc...) it seems music
could be represented by an array, where each element of the
array is the notes at that point in time/bar.  If the array represents
discrete time elements (say 1/128 of a bar, I am not very musical either)
then you can push notes into the proper array ref by a simple time calculation.

(aref music-notes 128) say translates to the notes starting in second bar at 2 sec.
                        (assuming allegro)

I am sure that there are more than one conceptually sound music representations.

The array would be pretty sparse in most cases, so it would take up some
unnecessary space.

I assume that you are only using hash tables to produce the final midi
sequence, so using it or alists would offer no substantial difference.
I do not think you are looking for blinding speed.

Wade
From: R. Mattes
Subject: Re: set like table
Date: 
Message-ID: <pan.2006.01.19.21.37.52.715194@mh-freiburg.de>
On Thu, 19 Jan 2006 18:45:58 +0000, xbunny wrote:

> Wade Humeniuk wrote:
> 
>> I cleaned up the loop a bit
> 
> thanks wade, I have been debating in my head whether the hash table is 
> the best data structure to use here. The other opion I found was an 
> associative list but I dont think it offers me any advantages here. Is 
> that right or have I over looked something?

Hmm, correct me if i'm wrong, but don't you have a pile of
midi note-on and note-off events that you need to sort according
to their time?

Why don't you just push your low-level midi-events ("short-midi")
on a vector and then sort it with 'sort' and an appropriate 'key'
function?

 HTH Ralf Mattes

P.S: you know about CommonMusic?
 
> Cheers Bunny
From: R. Mattes
Subject: Re: set like table
Date: 
Message-ID: <pan.2006.01.19.21.54.51.377416@mh-freiburg.de>
On Thu, 19 Jan 2006 22:37:53 +0100, R. Mattes wrote:

>
> Why don't you just push your low-level midi-events ("short-midi")
> on a vector and then sort it with 'sort' and an appropriate 'key'
> function?

;;; Something like the following:

(defun notes->event-list (list-of-notes)
  "Take a list of notes and convert it to
   a list of note-on/note-off events ordered
   by time."
   ;; BTW, you might have a good estimate about the final size of the 
   ;; event vector. Adust the deimension ...
  (let ((evector (make-array 100 :fill-pointer t :element-type 'short-midi)))
    (loop
       :for note :in note-list
       :do(let ((note-on (make-short-midi :channel (note-channel note)
					     :event *midi-note-on*
					     :velocity (note-attack-velocity note)
					     :note (note-note note)))
		   (note-off (make-short-midi :channel (note-channel note)
					      :event *midi-note-off*
					      :velocity (note-release-velocity note)
					      :note (note-note note))))

	    (vector-push-extend (cons (note-start-time note) note-on))
	    (vector-push-extend (cons (+ (note-start-time note)
					 (note-duration note))  note-off))))
    (setf evector (sort evector #'< :key #'car))
    (loop :for time/event :across evector :collect (cdr event/time)))

BTW, is there a reason your short-midi structure doesn't have a time slot.
With such a slot, writing a midi file might be much easier :-)

 Cheers, RalfD

>  HTH Ralf Mattes
> 
> P.S: you know about CommonMusic?
>  
>> Cheers Bunny
From: R. Mattes
Subject: Re: set like table
Date: 
Message-ID: <pan.2006.01.19.22.00.09.339450@mh-freiburg.de>
On Thu, 19 Jan 2006 22:54:51 +0100, R. Mattes wrote:


- 	    (vector-push-extend (cons (note-start-time note) note-on))
- 	    (vector-push-extend (cons (+ (note-start-time note)
- 					 (note-duration note))  note-off))))
+	    (vector-push-extend (cons (note-start-time note) note-on) evector)
+	    (vector-push-extend (cons (+ (note-start-time note)
					 (note-duration note)) note-off) evector)))


Definitely too late for me :-/

 Cheers, RalfD
From: xbunny
Subject: Re: set like table
Date: 
Message-ID: <JkVzf.160500$vl2.47217@fe2.news.blueyonder.co.uk>
R. Mattes wrote:

> 
> BTW, is there a reason your short-midi structure doesn't have a time slot.
> With such a slot, writing a midi file might be much easier :-)

The midi stream of short-midi events doesnt have a timeslot, instead 
only the time different between one event and the next is stored. There 
is also space for a reserved field which is 0 for note sequences. Thus a 
stream might something like
0  0 (midi-note-on :channel 0 :note middle-c)
0  0 (midi-note-on :channel 1 :note middle-c)
48 0 (midi-note-off :channel 0 :note middle-c)
0  0 (midi-note-off :chennel 1 :note middle-c)
48 0 (midi-note-on :channel 0 :note middle-c)
0  0 (midi-note-on :channel 1 :note middle-c)
48 0 (midi-note-off :channel 0 :note middle-c)
0  0 (midi-note-off :chennel 1 :note middle-c)

which would play 2 middle C's on channels 0 and 1 at the same time, stop 
them a two beats later and then repeat two beats after that. Notice that 
time difference between events 3 and 4 is zero since they both start at 
the same time. I've never written a standard midi file but as far as I 
know they are stored this way too, for me the buffer goes straight to 
the midi hardware (via CFFI and the win32 midi stream functions).

Best regards Bunny
From: R. Mattes
Subject: Re: set like table
Date: 
Message-ID: <pan.2006.01.20.14.06.35.108158@mh-freiburg.de>
On Thu, 19 Jan 2006 23:32:57 +0000, xbunny wrote:

> R. Mattes wrote:
> 
>> 
>> BTW, is there a reason your short-midi structure doesn't have a time slot.
>> With such a slot, writing a midi file might be much easier :-)
> 
> The midi stream of short-midi events doesnt have a timeslot, instead 
> only the time different between one event and the next is stored.

Hmm, i don't know what you finally want to do with these streams but 
with your design it's pretty hard to work with more than one event
stream. 
With a time slot you'd be able to easily merge two or more streams, or
you could insert one stream into another at a given time offset etc.

> There 
> is also space for a reserved field which is 0 for note sequences. Thus a 
> stream might something like
> 0  0 (midi-note-on :channel 0 :note middle-c)
> 0  0 (midi-note-on :channel 1 :note middle-c)
> 48 0 (midi-note-off :channel 0 :note middle-c)
> 0  0 (midi-note-off :chennel 1 :note middle-c)
> 48 0 (midi-note-on :channel 0 :note middle-c)
> 0  0 (midi-note-on :channel 1 :note middle-c)
> 48 0 (midi-note-off :channel 0 :note middle-c)
> 0  0 (midi-note-off :chennel 1 :note middle-c)
> 
> which would play 2 middle C's on channels 0 and 1 at the same time, stop 
> them a two beats later and then repeat two beats after that. Notice that 
> time difference between events 3 and 4 is zero since they both start at 
> the same time. I've never written a standard midi file but as far as I 
> know they are stored this way too, for me the buffer goes straight to 
> the midi hardware (via CFFI and the win32 midi stream functions).

Ah, i see, i usually use PortMidi which seems to be pretty portable
(and there are CFFI-bindings as well). For more involved Midi-stuff
there's also MidiShare (with bindings in the CM distribution).

Cheers RalfD

> Best regards Bunny
From: Peter Seibel
Subject: Re: set like table
Date: 
Message-ID: <m2d5idxu5b.fsf@gigamonkeys.com>
"R. Mattes" <··@mh-freiburg.de> writes:

> On Thu, 19 Jan 2006 18:45:58 +0000, xbunny wrote:
>
>> Wade Humeniuk wrote:
>> 
>>> I cleaned up the loop a bit
>> 
>> thanks wade, I have been debating in my head whether the hash table
>> is the best data structure to use here. The other opion I found was
>> an associative list but I dont think it offers me any advantages
>> here. Is that right or have I over looked something?
>
> Hmm, correct me if i'm wrong, but don't you have a pile of midi
> note-on and note-off events that you need to sort according to their
> time?

A heap might be a good datastructure to look at for this.

-Peter

-- 
Peter Seibel           * ·····@gigamonkeys.com
Gigamonkeys Consulting * http://www.gigamonkeys.com/
Practical Common Lisp  * http://www.gigamonkeys.com/book/
From: xbunny
Subject: Re: set like table - no sorry map like!
Date: 
Message-ID: <O4Rzf.159183$D47.139681@fe3.news.blueyonder.co.uk>
xbunny wrote:

> The problem I see with this is that I rely on the entries in the hash 
> table being sorted and I dont think this behaviour is garanteed. In the 
> original c++ I used a std::set and its something with thats 
> functionality that I require. Overall I get the feeling that what I am 
> trying to acheive can be done much tidily so any advice would be greatly 
> appreciated.

oops its like a std::map not a std::set I hope I still got across what I 
was trying to say, an ordered associative array.

Cheers Bunny
From: Pascal Bourguignon
Subject: Re: set like table
Date: 
Message-ID: <87psmom4ur.fsf@thalassa.informatimago.com>
xbunny <······@eidosnet.co.uk> writes:

> Im very new to Lisp and was wondering if someone could help me out
> with the most correct way to do the following.
>
> In my midi sequencer I define phrases of notes using lists of the
> following structure:
>
> (defstruct note
>    (start-time 0 :type fixnum)
>    (duration 0 :type fixnum)
>    (channel 0 :type fixnum)
>    (note 0 :type fixnum)
>    (attack-velocity 0 :type fixnum)
>    (release-velocity 0 :type fixnum))


I would rather use (integer 0 30000) or whatever bounds you may have
logically in your application than fixnum, because a fixnum can be any
size greater than 16 bits, perhaps smaller than what you need.

You can also define your own type:

(deftype small-signed-integer () `(integer -100000 100000))
and use it instead of the implementation specific fixnum.


> What I need to do is mix many phrases into one continuous stream of
> midi events. These things:
>
> ;; intermediate structure stored in the hash table of notes
> ;; ready for packing into a midi buffer
> (defstruct short-midi
>    (channel :byte)
>    (event :byte)
>    (velocity :byte)
>    (note :byte))

Strange.  Do you really want to initialize the channel to the keyword :byte?
--> (channel 0 :type :byte), etc

> What I do is use a hash table with each hash table key being the note
> event time and the value being the list of events that happen at that
> time. Thus I have this which places the phrase into the hash
> table... (please excuse if mozilla wraps the source)
> [...]
> The problem I see with this is that I rely on the entries in the hash
> table being sorted and I dont think this behaviour is garanteed. In
> the original c++ I used a std::set and its something with thats
> functionality that I require. Overall I get the feeling that what I am
> trying to acheive can be done much tidily so any advice would be
> greatly appreciated.

Indeed, an hash-table is not the indicated data structure.


You've got two problems: 
- convert a list of notes into a sorted list of event.
- _merge_ several sorted lists of events.

For the second, you'll merely use MERGE.

(defun merge-sequences (sequences)
  (reduce (lambda (a b) (merge 'list a b (function <=) :key (function car)))
          sequences :initial-value '()))


The first can be solved simply by processing the notes, generating the
events, an sorting them:

(defun notes-to-events (notes)
  (sort (mapcan
         (lambda (n)
           (list (cons (note-start-time n)
                       (make-short-midi :channel (note-channel n)
                                        :event *midi-note-on*
                                        :velocity (note-attack-velocity n)
                                        :note (note-note n)))
                 (cons (+ (note-start-time n) (note-duration n))
                       (make-short-midi :channel (note-channel n)
                                        :event *midi-note-off*
                                        :velocity (note-release-velocity n)
                                        :note (note-note n)))))
         notes)
        (function <=) :key (function car)))


Then:

  (merge-sequences (mapcar (function notes-to-events) list-of-note-sequences))


Since events must always be situated in time, you'd need to add a time
attribute to the short-midi structure.  Then you can do without the
CONS and you'd replace (function car) by (function short-midi-time).





(defparameter  s1
         (mapcar (let ((note 0)) (lambda (x)
                                    (make-note :start-time (first x)
                                               :duration (second x)
                                               :note (incf note))))
                 '((0 4) (4 4) (8 4) (12 4) (16 4))))

(defparameter  s2
         (mapcar (let ((note 0)) (lambda (x)
                                    (make-note :start-time (first x)
                                               :duration (second x)
                                               :note (incf note))))
               '((0 3) (3 3) (6 3) (9 3) (12 3))))

(defparameter  s3
         (mapcar (let ((note 0)) (lambda (x)
                                    (make-note :start-time (first x)
                                               :duration (second x)
                                               :note (incf note))))
               '((0 2) (2 2) (4 2) (8 8) )))
(defparameter *midi-note-on* :note-on)
(defparameter *midi-note-off* :note-off)
(merge-sequences (mapcar (function notes-to-events) (list s1 s2 s3)))
-->
((0 . #S(SHORT-MIDI :CHANNEL 0 :EVENT :NOTE-ON :VELOCITY 0 :NOTE 21))
 (0 . #S(SHORT-MIDI :CHANNEL 0 :EVENT :NOTE-ON :VELOCITY 0 :NOTE 11))
 (0 . #S(SHORT-MIDI :CHANNEL 0 :EVENT :NOTE-ON :VELOCITY 0 :NOTE 1))
 (2 . #S(SHORT-MIDI :CHANNEL 0 :EVENT :NOTE-ON :VELOCITY 0 :NOTE 22))
 (2 . #S(SHORT-MIDI :CHANNEL 0 :EVENT :NOTE-OFF :VELOCITY 0 :NOTE 21))
 (3 . #S(SHORT-MIDI :CHANNEL 0 :EVENT :NOTE-ON :VELOCITY 0 :NOTE 12))
 (3 . #S(SHORT-MIDI :CHANNEL 0 :EVENT :NOTE-OFF :VELOCITY 0 :NOTE 11))
 (4 . #S(SHORT-MIDI :CHANNEL 0 :EVENT :NOTE-ON :VELOCITY 0 :NOTE 23))
 (4 . #S(SHORT-MIDI :CHANNEL 0 :EVENT :NOTE-OFF :VELOCITY 0 :NOTE 22))
 (4 . #S(SHORT-MIDI :CHANNEL 0 :EVENT :NOTE-ON :VELOCITY 0 :NOTE 2))
 (4 . #S(SHORT-MIDI :CHANNEL 0 :EVENT :NOTE-OFF :VELOCITY 0 :NOTE 1))
 (6 . #S(SHORT-MIDI :CHANNEL 0 :EVENT :NOTE-OFF :VELOCITY 0 :NOTE 23))
 (6 . #S(SHORT-MIDI :CHANNEL 0 :EVENT :NOTE-ON :VELOCITY 0 :NOTE 13))
 (6 . #S(SHORT-MIDI :CHANNEL 0 :EVENT :NOTE-OFF :VELOCITY 0 :NOTE 12))
 (8 . #S(SHORT-MIDI :CHANNEL 0 :EVENT :NOTE-ON :VELOCITY 0 :NOTE 24))
 (8 . #S(SHORT-MIDI :CHANNEL 0 :EVENT :NOTE-ON :VELOCITY 0 :NOTE 3))
 (8 . #S(SHORT-MIDI :CHANNEL 0 :EVENT :NOTE-OFF :VELOCITY 0 :NOTE 2))
 (9 . #S(SHORT-MIDI :CHANNEL 0 :EVENT :NOTE-ON :VELOCITY 0 :NOTE 14))
 (9 . #S(SHORT-MIDI :CHANNEL 0 :EVENT :NOTE-OFF :VELOCITY 0 :NOTE 13))
 (12 . #S(SHORT-MIDI :CHANNEL 0 :EVENT :NOTE-ON :VELOCITY 0 :NOTE 15))
 (12 . #S(SHORT-MIDI :CHANNEL 0 :EVENT :NOTE-OFF :VELOCITY 0 :NOTE 14))
 (12 . #S(SHORT-MIDI :CHANNEL 0 :EVENT :NOTE-ON :VELOCITY 0 :NOTE 4))
 (12 . #S(SHORT-MIDI :CHANNEL 0 :EVENT :NOTE-OFF :VELOCITY 0 :NOTE 3))
 (15 . #S(SHORT-MIDI :CHANNEL 0 :EVENT :NOTE-OFF :VELOCITY 0 :NOTE 15))
 (16 . #S(SHORT-MIDI :CHANNEL 0 :EVENT :NOTE-OFF :VELOCITY 0 :NOTE 24))
 (16 . #S(SHORT-MIDI :CHANNEL 0 :EVENT :NOTE-ON :VELOCITY 0 :NOTE 5))
 (16 . #S(SHORT-MIDI :CHANNEL 0 :EVENT :NOTE-OFF :VELOCITY 0 :NOTE 4))
 (20 . #S(SHORT-MIDI :CHANNEL 0 :EVENT :NOTE-OFF :VELOCITY 0 :NOTE 5)))


-- 
__Pascal Bourguignon__                     http://www.informatimago.com/

Nobody can fix the economy.  Nobody can be trusted with their finger
on the button.  Nobody's perfect.  VOTE FOR NOBODY.
From: xbunny
Subject: Re: set like table
Date: 
Message-ID: <LDVzf.160828$vl2.3484@fe2.news.blueyonder.co.uk>
Pascal Bourguignon wrote:

> xbunny <······@eidosnet.co.uk> writes:

>>;; intermediate structure stored in the hash table of notes
>>;; ready for packing into a midi buffer
>>(defstruct short-midi
>>   (channel :byte)
>>   (event :byte)
>>   (velocity :byte)
>>   (note :byte))
> 
> 
> Strange.  Do you really want to initialize the channel to the keyword :byte?
> --> (channel 0 :type :byte), etc

yes that makes alot more sense.

> 
> Indeed, an hash-table is not the indicated data structure.
> 
> 
> You've got two problems: 
> - convert a list of notes into a sorted list of event.
> - _merge_ several sorted lists of events.

[snip great code]

wow thats alot to take in, thanks :-)

> (merge-sequences (mapcar (function notes-to-events) (list s1 s2 s3)))
> -->
> ((0 . #S(SHORT-MIDI :CHANNEL 0 :EVENT :NOTE-ON :VELOCITY 0 :NOTE 21))
>  (0 . #S(SHORT-MIDI :CHANNEL 0 :EVENT :NOTE-ON :VELOCITY 0 :NOTE 11))
>  (0 . #S(SHORT-MIDI :CHANNEL 0 :EVENT :NOTE-ON :VELOCITY 0 :NOTE 1))
[etc]

It seems I still need to convert this list to a proper short-midi stream 
but that should be easy by traversing the list and calculating the time 
differences between events as I did before.

Thanks again you have given me great food for thought. Seem like I need 
to read the reference on merge.

Regards, Bunny
From: xbunny
Subject: Re: set like table
Date: 
Message-ID: <YuXzf.162466$vl2.23368@fe2.news.blueyonder.co.uk>
Pascal Bourguignon wrote:

> 
> (defparameter  s1
>          (mapcar (let ((note 0)) (lambda (x)
>                                     (make-note :start-time (first x)
>                                                :duration (second x)
>                                                :note (incf note))))
>                  '((0 4) (4 4) (8 4) (12 4) (16 4))))
> 
> (defparameter  s2
>          (mapcar (let ((note 0)) (lambda (x)
>                                     (make-note :start-time (first x)
>                                                :duration (second x)
>                                                :note (incf note))))
>                '((0 3) (3 3) (6 3) (9 3) (12 3))))
> 
> (defparameter  s3
>          (mapcar (let ((note 0)) (lambda (x)
>                                     (make-note :start-time (first x)
>                                                :duration (second x)
>                                                :note (incf note))))
>                '((0 2) (2 2) (4 2) (8 8) )))
> (defparameter *midi-note-on* :note-on)
> (defparameter *midi-note-off* :note-off)
> (merge-sequences (mapcar (function notes-to-events) (list s1 s2 s3)))
> -->
> ((0 . #S(SHORT-MIDI :CHANNEL 0 :EVENT :NOTE-ON :VELOCITY 0 :NOTE 21))
>  (0 . #S(SHORT-MIDI :CHANNEL 0 :EVENT :NOTE-ON :VELOCITY 0 :NOTE 11))
>  (0 . #S(SHORT-MIDI :CHANNEL 0 :EVENT :NOTE-ON :VELOCITY 0 :NOTE 1))
>  (2 . #S(SHORT-MIDI :CHANNEL 0 :EVENT :NOTE-ON :VELOCITY 0 :NOTE 22))
>  (2 . #S(SHORT-MIDI :CHANNEL 0 :EVENT :NOTE-OFF :VELOCITY 0 :NOTE 21))

Looking at the results above and your code am I right in assuming that 
the initial note in phrase s3 was 20 not 0 (and 10 for s2 and 0 for s1). 
This being the case is there anyway to make your code output the note-on 
and note-off events in the correct order in the results? I ask because 
at time 2 the stream instructs the instrument to begin note 22 and then 
end note 21 rather than ending the first note before beginning the 
second. if the phrase played the same two notes in immediate succession 
then the note off for the first note would appear after the note on for 
the second note which would be incorrect as the second note would be 
immediately turned off as the events are processed in the order they 
appear in the midi stream. Ie despite them being the 'same' time the 
instrument can only processes them serially and a note-off ends any 
note-ons for that note. I guess this is a throw back of using a keyboard 
as a midi controller you cant hit the same key twice without lifting 
your finger!

I hope thats not too confusing and Im sorry that I never specifed that 
the order of midi events was critical.

Best regards Bunny
From: Pascal Bourguignon
Subject: Re: set like table
Date: 
Message-ID: <87r773k1dv.fsf@thalassa.informatimago.com>
xbunny <······@eidosnet.co.uk> writes:

> Pascal Bourguignon wrote:
>> [...]
>> (defparameter *midi-note-on* :note-on)
>> (defparameter *midi-note-off* :note-off)
>> (merge-sequences (mapcar (function notes-to-events) (list s1 s2 s3)))
>> -->
>> ((0 . #S(SHORT-MIDI :CHANNEL 0 :EVENT :NOTE-ON :VELOCITY 0 :NOTE 21))
>>  (0 . #S(SHORT-MIDI :CHANNEL 0 :EVENT :NOTE-ON :VELOCITY 0 :NOTE 11))
>>  (0 . #S(SHORT-MIDI :CHANNEL 0 :EVENT :NOTE-ON :VELOCITY 0 :NOTE 1))
>>  (2 . #S(SHORT-MIDI :CHANNEL 0 :EVENT :NOTE-ON :VELOCITY 0 :NOTE 22))
>>  (2 . #S(SHORT-MIDI :CHANNEL 0 :EVENT :NOTE-OFF :VELOCITY 0 :NOTE 21))
>
> Looking at the results above and your code am I right in assuming that
> the initial note in phrase s3 was 20 not 0 (and 10 for s2 and 0 for
> s1). This being the case is there anyway to make your code output the
> note-on and note-off events in the correct order in the results? I ask
> because at time 2 the stream instructs the instrument to begin note 22
> and then end note 21 rather than ending the first note before
> beginning the second. if the phrase played the same two notes in
> immediate succession then the note off for the first note would appear
> after the note on for the second note which would be incorrect as the
> second note would be immediately turned off as the events are
> processed in the order they appear in the midi stream. Ie despite them
> being the 'same' time the instrument can only processes them serially
> and a note-off ends any note-ons for that note. I guess this is a
> throw back of using a keyboard as a midi controller you cant hit the
> same key twice without lifting your finger!
>
> I hope thats not too confusing and Im sorry that I never specifed that
> the order of midi events was critical.

It should not matter much, given the usual release velocities, but you
can easily order them as you want just changing the sort order
predicate:

(defun notes-to-events (notes)
  (sort (mapcan
         (lambda (n)
           (list (cons (note-start-time n)
                       (make-short-midi :channel (note-channel n)
                                        :event *midi-note-on*
                                        :velocity (note-attack-velocity n)
                                        :note (note-note n)))
                 (cons (+ (note-start-time n) (note-duration n))
                       (make-short-midi :channel (note-channel n)
                                        :event *midi-note-off*
                                        :velocity (note-release-velocity n)
                                        :note (note-note n)))))
         notes)
        (lambda (a b)
           (or (< (car a) (car b))
               (and (= (car a) (car b))
                    (eql *midi-note-off* (short-midi-event (cdr b))))))))

If you want to order serveral different kind of events, instead of eql
you could write:

(<= (position (short-midi-event (cdr a)) *event-order*)
    (position (short-midi-event (cdr b)) *event-order*))

With: 

(defparameter *event-order* (list *midi-note-off*  *midi-note-on* ...))

 
-- 
__Pascal Bourguignon__                     http://www.informatimago.com/

CONSUMER NOTICE: Because of the "uncertainty principle," it is
impossible for the consumer to simultaneously know both the precise
location and velocity of this product.