Mercurial > hg > audiodb
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 |# |