comparison bindings/sb-alien/interface.lisp @ 511:3141e51cb077

Initial checkin of WIP sb-alien bindings. Includes some analogues to libtests/ test files in tests.lisp Coverage of the C API is not complete, but I think there's enough to implement all the same functionality tests as there are in the libtests/ suite.
author mas01cr
date Wed, 21 Jan 2009 21:48:25 +0000
parents
children 633614461994
comparison
equal deleted inserted replaced
510:2b95e396f4d7 511:3141e51cb077
1 (in-package "SB-ADB")
2
3 (defclass adb ()
4 ((alien :initarg alien)))
5 (defmethod initialize-instance :after ((o adb) &key)
6 (when (and (slot-boundp o 'alien)
7 (not (null-alien (slot-value o 'alien))))
8 (let ((alien (slot-value o 'alien)))
9 (sb-ext:finalize o (lambda () (%close alien))))))
10
11 ;;; FIXME: deal with interrupt-safety / leak issues
12
13 ;;; FIXME: if-does-not-exist.
14 (defun open (path &key (direction :input) (if-exists :error) (adb-class 'adb))
15 (flet ((direction-flag (direction)
16 (ecase direction
17 ((:input :probe) sb-posix:o-rdonly)
18 ((:output :io) sb-posix:o-rdwr))))
19 (let* ((truepath (probe-file path))
20 (alien (cond
21 (truepath
22 (ecase direction
23 ((:input :probe)
24 (%%open path (direction-flag direction)))
25 ((:output :io)
26 (case if-exists
27 (:error (error "database already exists: ~S" path))
28 (:append (%open path (direction-flag direction)))
29 ;; FIXME: not the best implementation of
30 ;; :SUPERSEDE semantics ever.
31 (:supersede (delete-file path) (%create path 0 0 0))))))
32 ((eql direction :input)
33 (error "database does not exist: ~S" path))
34 (t (%create path 0 0 0)))))
35 (cond
36 ((null-alien alien)
37 (case direction
38 (:probe nil)
39 (t (error "opening database failed: ~S" path))))
40 (t (make-instance adb-class 'alien alien))))))
41 (defmethod close ((o adb))
42 (when (and (slot-boundp o 'alien)
43 (not (null-alien (slot-value o 'alien))))
44 (%close (slot-value o 'alien))
45 (sb-ext:cancel-finalization o)
46 (slot-makunbound o 'alien)))
47 (defmacro with-adb ((adb path &rest open-args &key direction adb-class if-exists)
48 &body body)
49 (declare (ignore direction adb-class if-exists))
50 `(let ((,adb (open ,path ,@open-args)))
51 (unwind-protect
52 (locally ,@body)
53 (close ,adb))))
54
55 (defgeneric l2norm (db))
56 (defmethod l2norm ((db adb))
57 (%l2norm (slot-value db 'alien)))
58
59 (defstruct (datum
60 (:constructor %make-datum)
61 (:constructor
62 make-datum
63 (key %data &key times power
64 &aux (data
65 (make-array (list (length %data) (length (elt %data 0)))
66 :element-type 'double-float
67 :initial-contents %data)))))
68 (key (error "missing argument") :type string)
69 (data (error "missing argument") :type (simple-array double-float (* *)))
70 (times nil :type (or null (simple-array double-float)))
71 (power nil :type (or null (simple-array double-float))))
72
73 (defgeneric insert (datum db))
74
75 (defmethod insert ((datum datum) (db adb))
76 (let* ((data (datum-data datum))
77 (nvectors (array-dimension data 0))
78 (dim (array-dimension data 1)))
79 (when (datum-times datum)
80 (unless (= (array-total-size (datum-times datum)) (* 2 nvectors))
81 (error "dimension mismatch for times: ~S" datum)))
82 (when (datum-power datum)
83 (unless (= (array-total-size (datum-power datum)) nvectors)
84 (error "dimension mismatch for power: ~S" datum)))
85 (with-alien ((d adb-datum-t))
86 (sb-sys:with-pinned-objects ((datum-data datum)
87 (datum-times datum)
88 (datum-power datum))
89 (setf (slot d 'dim) dim)
90 (setf (slot d 'nvectors) nvectors)
91 (setf (slot d 'key) (datum-key datum))
92 (setf (slot d 'data)
93 (sb-sys:vector-sap (sb-ext:array-storage-vector (datum-data datum))))
94 (if (datum-times datum)
95 (setf (slot d 'times) (sb-sys:vector-sap (sb-ext:array-storage-vector (datum-times datum))))
96 (setf (slot d 'times) nil))
97 (if (datum-power datum)
98 (setf (slot d 'power) (sb-sys:vector-sap (sb-ext:array-storage-vector (datum-times datum))))
99 (setf (slot d 'power) nil))
100 (sb-int:with-float-traps-masked (:invalid)
101 (%insert-datum (slot-value db 'alien) (addr d)))))))
102
103 (defstruct result
104 (key "" :type string)
105 (distance 0d0 :type double-float)
106 (qpos 0 :type (and unsigned-byte fixnum))
107 (ipos 0 :type (and unsigned-byte fixnum)))
108
109 ;;; Hrm. To copy (from the malloc heap) or not to copy? Copying
110 ;;; would make things generally easier, I guess, and we have to hope
111 ;;; that the order of magnitude is not such that the copying causes
112 ;;; pain.
113 (defclass results (sequence standard-object)
114 ())
115 (defclass copied-query-results (results)
116 ((results :initarg results :accessor %copied-results)))
117 (defmethod print-object ((o copied-query-results) s)
118 (pprint-logical-block (s nil)
119 (print-unreadable-object (o s :type t)
120 (format s "(~D results):~2I~@:_" (length o))
121 (sequence:dosequence (r o)
122 (pprint-pop)
123 (format s "~A ~6,3e ~D ~D~@:_"
124 (result-key r) (result-distance r)
125 (result-qpos r) (result-ipos r))))))
126
127 (defmethod sequence:length ((o copied-query-results))
128 (length (%copied-results o)))
129 (defmethod sequence:elt ((o copied-query-results) index)
130 (elt (%copied-results o) index))
131 (defmethod (setf sequence:elt) (new-value (o copied-query-results) index)
132 (setf (elt (%copied-results o) index) new-value))
133 (defmethod sequence:make-sequence-like
134 ((o copied-query-results) length &rest args
135 &key initial-element initial-contents)
136 (declare (ignore initial-element initial-contents))
137 (let ((vector (apply #'make-array length args)))
138 (make-instance 'copied-query-results 'results vector)))
139 (defmethod sequence:adjust-sequence
140 ((o copied-query-results) length &rest args
141 &key initial-element initial-contents)
142 (declare (ignore initial-element initial-contents))
143 (let ((results (%copied-results o)))
144 (apply #'sequence:adjust-sequence results length args))
145 o)
146
147 (defclass proxied-query-results (results)
148 ((adb :initarg adb)
149 (spec :initarg spec)
150 (results :initarg results)))
151 (defmethod initialize-instance :after ((o proxied-query-results) &key)
152 (when (and (slot-boundp o 'results)
153 (not (null-alien o))
154 (slot-boundp o 'spec))
155 (with-slots (results spec adb) o
156 (flet ((results-finalizer ()
157 (with-slots (alien) adb
158 (%free-query-results alien spec results))))
159 (sb-ext:finalize o #'results-finalizer)))))
160
161 (defgeneric query (datum db &key))
162
163 ;;; FIXME: I don't like this way of generalizing the boilerplate;
164 ;;; isn't there a nice functional way of doing this?
165 (macrolet
166 ((def (name datum-class &body qdatum-forms)
167 `(defmethod ,name ((datum ,datum-class) (db adb) &key
168 (sequence-length 1) (sequence-start 0)
169 exhaustivep accumulation distance
170 ;; FIXME: dubious historical defaults
171 (npoints 10) (ntracks 10)
172
173 (radius nil radiusp)
174 (include-keys nil include-keys-p)
175 (exclude-keys nil exclude-keys-p))
176 (unless (slot-boundp db 'alien)
177 (error "database ~S is closed" db))
178 (with-alien ((qid adb-query-id-t)
179 (qparams adb-query-parameters-t)
180 (qrefine adb-query-refine-t)
181 (qdatum adb-datum-t))
182 ,@qdatum-forms
183 (setf (slot qid 'datum) (addr qdatum))
184 (setf (slot qid 'sequence-length) sequence-length)
185 (setf (slot qid 'sequence-start) sequence-start)
186 (setf (slot qid 'flags) (if exhaustivep 1 0))
187
188 (setf (slot qparams 'accumulation)
189 (ecase accumulation
190 (:db 1)
191 (:per-track 2)
192 (:one-to-one 3)))
193 (setf (slot qparams 'distance)
194 (ecase distance
195 (:dot-product 1)
196 (:euclidean-normed 2)
197 (:euclidean 3)))
198 (setf (slot qparams 'npoints) (or npoints 0))
199 (setf (slot qparams 'ntracks) (or ntracks 0))
200
201 (let ((refine-flags 0))
202 (when radiusp
203 (setf refine-flags (logior refine-flags 4))
204 (setf (slot qrefine 'radius) (float radius 0d0)))
205 ;; FIXME: the freeing of the KEYS slot in these
206 ;; include/exclude keylists isn't interrupt-safe.
207 ;;
208 ;; FIXME: think quite hard about the behaviour of this
209 ;; when LENGTH is 0.
210 (when include-keys-p
211 (setf refine-flags (logior refine-flags 1))
212 (let ((length (length include-keys)))
213 (setf (slot (slot qrefine 'include) 'nkeys) length)
214 (let ((keys (make-alien c-string length)))
215 (setf (slot (slot qrefine 'include) 'keys) keys)
216 (loop for key being the elements of include-keys
217 for i upfrom 0
218 do (setf (deref keys i) key)))))
219 (when exclude-keys-p
220 (setf refine-flags (logior refine-flags 2))
221 (let ((length (length exclude-keys)))
222 (setf (slot (slot qrefine 'exclude) 'nkeys) length)
223 (let ((keys (make-alien c-string length)))
224 (setf (slot (slot qrefine 'exclude) 'keys) keys)
225 (loop for key being the elements of exclude-keys
226 for i upfrom 0
227 do (setf (deref keys i) key)))))
228 (setf (slot qrefine 'flags) refine-flags))
229 (setf (slot qrefine 'hopsize) 1)
230
231 ;; FIXME: hm, this possibly suggests that there's something
232 ;; a bit wrong with the C audioDB interface. The API
233 ;; currently exposed effectively requires either that all
234 ;; the processing of query results happens in the same
235 ;; dynamic extent as the call to audiodb_query_spec(), or
236 ;; that the adb_query_spec_t object is allocated on the
237 ;; heap. We need to think harder about whether the spec
238 ;; argument is really required (I think it probably isn't).
239 ;;
240 ;; meanwhile, here we're using it with dynamic extent anyway, so
241 ;; we could put it right back on the stack.
242 (let ((qspec (make-alien adb-query-spec-t)))
243 (unwind-protect
244 (progn
245 (setf (slot qspec 'qid) qid)
246 (setf (slot qspec 'params) qparams)
247 (setf (slot qspec 'refine) qrefine)
248
249 (let ((results
250 (sb-int:with-float-traps-masked (:invalid)
251 (%query (slot-value db 'alien) qspec))))
252 (flet ((collect-copied-results ()
253 (let ((nresults (slot results 'nresults))
254 (cresults (slot results 'results)))
255 (coerce
256 (loop for i below nresults
257 for r = (deref cresults i)
258 collect (make-result
259 :key (slot r 'key)
260 :distance (slot r 'dist)
261 :qpos (slot r 'qpos)
262 :ipos (slot r 'ipos)))
263 'vector))))
264 (unwind-protect
265 (make-instance 'copied-query-results
266 'results (collect-copied-results))
267 (%free-query-results (slot-value db 'alien) qspec results)))))
268 (when (logbitp 0 (slot (slot qspec 'refine) 'flags))
269 (free-alien (slot (slot (slot qspec 'refine) 'include) 'keys)))
270 (when (logbitp 1 (slot (slot qspec 'refine) 'flags))
271 (free-alien (slot (slot (slot qspec 'refine) 'exclude) 'keys)))
272 (free-alien qspec)))))))
273 (def query string (setf (slot qdatum 'key) datum))
274 (def query datum
275 (setf (slot qdatum 'key) (datum-key datum))
276 (setf (slot qdatum 'dim) (array-dimension (datum-data datum) 1))
277 (setf (slot qdatum 'nvectors) (array-dimension (datum-data datum) 0))
278 (setf (slot qdatum 'data) (sb-sys:vector-sap (sb-ext:array-storage-vector (datum-data datum))))
279 (if (datum-times datum)
280 (setf (slot qdatum 'times) (sb-sys:vector-sap (sb-ext:array-storage-vector (datum-times datum))))
281 (setf (slot qdatum 'times) nil))
282 (if (datum-power datum)
283 (setf (slot qdatum 'power) (sb-sys:vector-sap (sb-ext:array-storage-vector (datum-times datum))))
284 (setf (slot qdatum 'power) nil))))
285
286 #+test
287 (sb-adb:with-adb (db "/home/csr21/tmp/omras2-workshop/9.adb")
288 (sb-adb:query "KSA_CHARM_337" db :exhaustivep t :sequence-length 30
289 :accumulation :per-track :distance :euclidean :npoints 1 :ntracks 20))
290
291 #+test
292 (sb-adb:with-adb (db "/home/csr21/tmp/omras2-workshop/9.adb")
293 (sb-adb:query "KSA_CHARM_337" db :sequence-start 20 :sequence-length 20
294 :accumulation :per-track :distance :euclidean-normed
295 :npoints 10 :ntracks 1))
296
297 #+test
298 (sb-adb:with-adb (db "/home/csr21/tmp/omras2-workshop/9.adb")
299 (sb-adb:query "KSA_CHARM_337" db
300 :exhaustivep t :sequence-length 30
301 :accumulation :per-track :distance :euclidean-normed
302 :npoints 2 :ntracks 10))
303
304 ;;; only hacks and tests below
305 #|
306 (defun foo ()
307 (let ((db (%open "/home/csr21/tmp/omras2-workshop/9.adb" sb-posix:o-rdonly)))
308 (unless (null-alien db)
309 (unwind-protect
310 (with-alien ((status adb-status-t))
311 (%status db (addr status))
312 (print (list (slot status 'dim) (slot status 'nfiles))))
313 (%close db)))))
314
315 (defun set-up-spec (spec qid qparams qrefine)
316 (declare (type (alien adb-query-parameters-t) qparams)
317 (type (alien adb-query-refine-t) qrefine)
318 (type (alien adb-query-id-t) qid)
319 (type (alien adb-query-spec-t) spec))
320 (setf (slot spec 'refine) qrefine)
321 nil)
322
323 (defun bar ()
324 (let ((db (%open "/home/csr21/tmp/omras2-workshop/9.adb" sb-posix:o-rdonly)))
325 (unless (null-alien db)
326 (unwind-protect
327 (with-alien ((qid adb-query-id-t)
328 (qparams adb-query-parameters-t)
329 (qrefine adb-query-refine-t)
330 (qspec adb-query-spec-t)
331 (datum adb-datum-t))
332 (setf (slot datum 'key) "KSA_CHARM_337")
333 (setf (slot datum 'data) (sap-alien (sb-sys:int-sap 0) (* double)))
334
335 (setf (slot qid 'datum) (addr datum))
336 (setf (slot qid 'sequence-length) 30)
337 (setf (slot qid 'flags) 1) ; ADB_QID_FLAG_EXHAUSTIVE
338
339 (setf (slot qparams 'accumulation) 2) ; ADB_ACCUMULATION_PER_TRACK
340 (setf (slot qparams 'distance) 2) ; ADB_DISTANCE_EUCLIDEAN_NORMED
341 (setf (slot qparams 'npoints) 1)
342 (setf (slot qparams 'ntracks) 20)
343
344 (setf (slot qrefine 'flags) 0)
345 (setf (slot qrefine 'hopsize) 1)
346
347 (setf (slot qspec 'qid) qid)
348 (setf (slot qspec 'params) qparams)
349 (setf (slot qspec 'refine) qrefine)
350 (let ((results (%query db (addr qspec))))
351 (unless (null-alien results)
352 (unwind-protect
353 (flet ((print-result (n)
354 (let ((result (deref (slot results 'results) n)))
355 (format t "~&~A ~F ~D ~D~%"
356 (slot result 'key) (slot result 'dist)
357 (slot result 'qpos) (slot result 'ipos)))))
358 (dotimes (i (slot results 'nresults))
359 (print-result i)))
360 (%free-query-results db (addr qspec) results)))))
361 (%close db)))))
362 |#