;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;

;; Impromptu spaces is based on Tuple Spaces

;; Tuple spaces first appeared in Linda 1972

;;

;; Spaces provides a mechanism for shared memory

;; transparently between processes 

;; and even across hosts.

;;

;; Tuples can be of any length and can include

;; numbers, strings and vectors

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


;; first let's add some data to spaces

;; we'll add a 3-tuple of {string,number,number}

;; tuples can be made up of any number of

;; strings, integers, reals and lists

(spaces:write "stuff" 1 55.5)


;; we read it back by matching against tuples in the space

;; 

;; stings match strings

;; numbers match numbers

;; vectors match vectors

;; #t matches anything

;;

;; You must match from the start of a tuple

;; but you do not need to match against the whole tuple

;;

;; for example, you can just match the first element of the tuple

;; read returns a list of matching tuples (a list of lists)

(define result (spaces:read "stuff"))

(print result)


;; if we add a new 4-tuple {string,num,num,vector}

(spaces:write "stuff" 1 66.6 '#(1 2 3))


;; matching against "stuff" will return both tuples

;; even though the tuples are different lengths

(print '-> (spaces:read "stuff"))


;; we could be more explicit with out tuple match

;; by matching against more of the tuple

(print '-> (spaces:read "stuff" 1 55.5))


;; tuples don't need to start with strings

(spaces:write 55.5 '#(1 2 3) 70)

(print '-> (spaces:read 55.5))


;; let's try some more matches

(spaces:write "morestuff" 1 77.7 '#(4 6 7))


;; we could return all tuples

;; whose 2nd element is 1

(print '-> (spaces:read #t 1))


;; you can use as many match criteria as you like

;; here we match explicitly against positions 2 and 4

;; #t matches against anything in positions 1 and 3

(print '-> (spaces:read #t 1 #t '#(1 2 3)))



;; by default spaces uses simple direct string matching

;; "string" == "string"

;; however, if efficiency is not a large concern

;; you can also use regular expression matching

;; by setting regex matches to true

;; (spaces:regex-string-match #t)

;; you can set back to #f at any time.

;;

;; then we can match against "morestuff" and "stuff"

(spaces:regex-string-match #t)

(print '-> (spaces:read "^.*stuff$"))


;; numbers can be searched by providing

;; a search predicate < > <= >= <>

;; with a number

;; as the two elements of a list

;; in the tuple position that we want to test

(print '-> (spaces:read #t #t '(<> . 66.6)))

(print '-> (spaces:read #t #t '(<= . 66.6)))

(print '-> (spaces:read #t #t '(< . 66.6)))

(print '-> (spaces:read #t #t '(> . 66.6)))


;; we can also match against vectors

(print '-> (spaces:read #t 1 #t '#(4 6 7)))


;; we can also do a type of read that also removes

;; the returned results from spaces

;; we use spaces:take-all for this purpose

(print '-> (spaces:take-all ".*stuff"))


;; if we now try to read ".*stuff"

;; you would expect an empty result

;; because spaces:take-all will have

;; removed everything.

;; However, spaces:read actually blocks

;; waiting for a match

(print '-> (spaces:read ".*stuff"))


;; so let's give it something to match

(spaces:write "andrewsstuff" 1 2 3)


;; note that spaces:read just returned a result

;; neat huh!

;; you can also ask take to remove just the 

;; first matched result.  This can be useful

;; as you can potentially add multiple similar 

;; tuples ... something like this

(spaces:write "andrewsstuff" 2 2 3)

(spaces:write "andrewsstuff" 3 2 3)

(spaces:write "andrewsstuff" 4 2 3)


(print '-> (spaces:read "andrewsstuff"))

;; take one at a time

(print '-> (spaces:take "andrewsstuff"))

(print '-> (spaces:take "andrewsstuff"))

(print '-> (spaces:take "andrewsstuff"))

(print '-> (spaces:take "andrewsstuff"))

;; try again!

(print '-> (spaces:take "andrewsstuff"))

;; whoops nothing left - better add another one

(spaces:write "andrewsstuff" 5 2 3)


;; spaces includes a bind operator that allows you

;; to bind symbols against the FIRST result of a query.

;; bind operates similarly to read accept that it 

;; does not return a result.  Instead it accepts

;; symbols in tuple positions and binds the result

;; value to the symbol.  bind looks similar to read

;; where #t's are replaced with symbols to bind


(spaces:write "andrewsstuff" 6 2 3)?A(R)(spaces:write "andrewsstuff" 7 2 3)

(print '-> (spaces:read "andrewsstuff" #t 2 #t))

(spaces:bind "andrewsstuff" 'var1 2 'var2)

(print var1 var2)

;; bind also has a bind take variant that removes

;; the FIRST matched result just like take

(spaces:bindt "andrewsstuff" 'var1 2 'var2)

(print var1 var2)

(spaces:bindt "andrewsstuff" 'var1 2 'var2)

(print var1 var2)


;; spaces:wait is the opposite of read

;; it blocks until a particular match

;; is NOT available in the spaces db

(spaces:write "test" 1 2 3)

(print 'done-waiting (spaces:wait "test" 1 2 3))

(spaces:delete "test" 1 2 3)



;; finally - and most importantly

;; you can use any process (on any host!)

;; as the host of the spaces db

;; you could use ipc:connect-to-process instead!

(ipc:new-process "my-new-proc" 9876)

(set! *spaces:process* "my-new-proc")


;; everything works exactly the same as before

(spaces:write "helloworld" 1 2 3)

(print 'from-my-new-proc-> (spaces:read "helloworld"))

(spaces:bindt 'var1 1 'var2 3)

(print var1 var2)

;; including blocking

(print 'from-my-new-proc-> (spaces:read "helloworld"))

(spaces:write "helloworld" 4 '#(1 2 3) "another string" 6)


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; a more concrete example


(spaces:regex-string-match #f)


(au:clear-graph)

;; first define dls

(define dls (au:make-node "aumu" "dls " "appl"))

(au:connect-node dls 0 *au:output-node* 0)

(au:update-graph)


;; start an infinite loop

;; spaces:bindt will block until it can match a tuple

;; spaces:bindt also removes the tuple that it matches

;; thus forcing it to block again

(define loop

   (lambda ()      

      (spaces:bindt "/mailbox/andrew/note" 'time 'pitch 'volume 'duration)

      (play-note (clock->samples time) dls pitch volume duration)

      (loop)))


;; start the loop

(loop)


;; add a new note

(spaces:write "/mailbox/andrew/note" (clock) 60 80 20000)


;; try adding three notes at once

(begin (spaces:write "/mailbox/andrew/note" (clock) 60 80 20000)

       (spaces:write "/mailbox/andrew/note" (clock) 63 80 20000)

       (spaces:write "/mailbox/andrew/note" (clock) 67 80 20000))


;; now start a second loop to control the first loop

;; this second loop could happily be in a second process

;; or even a remote host

(define loop2

   (lambda (beat) 

      (spaces:write "/mailbox/andrew/note" 

                    (samples->clock (*metro* beat))

                    (random 40 80)

                    (random 50 70)

                    3500)

      (callback (*metro* (+ beat (* .5 .5))) 'loop2 (+ beat .5))))


(loop2 (*metro* 'get-beat 4))



;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; if we wanted multiple clients all reading the same 

;; spaces db then we could either

;; a) write multiple times (with indexes for example)

;;    for each host so that bindt removed only that

;;    particular clients tuple

;; b) use spaces:wait and spaces:delete as the

;;    modifications below demonstrate. this example will

;;    work for any number of clients connected to the 

;;    same spaces db

(define loop

   (lambda ()      

      (spaces:bind "/mailbox/andrew/note" 'time 'pitch 'volume 'duration)

      (play-note (clock->samples time) dls pitch volume duration)

      (spaces:wait "/mailbox/andrew/note" time pitch volume duration)

      (loop)))


(define loop2

   (lambda (beat) 

      (spaces:delete "/mailbox/andrew/note")

      (spaces:write "/mailbox/andrew/note" 

                    (samples->clock (*metro* beat))

                    (random 40 80)

                    (random 50 70)

                    3500)

      (callback (*metro* (+ beat (* .5 .5))) 'loop2 (+ beat .5))))