Mercurial > hg > audiodb
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bindings/sb-alien/interface.lisp Wed Jan 21 21:48:25 2009 +0000 @@ -0,0 +1,362 @@ +(in-package "SB-ADB") + +(defclass adb () + ((alien :initarg alien))) +(defmethod initialize-instance :after ((o adb) &key) + (when (and (slot-boundp o 'alien) + (not (null-alien (slot-value o 'alien)))) + (let ((alien (slot-value o 'alien))) + (sb-ext:finalize o (lambda () (%close alien)))))) + +;;; FIXME: deal with interrupt-safety / leak issues + +;;; FIXME: if-does-not-exist. +(defun open (path &key (direction :input) (if-exists :error) (adb-class 'adb)) + (flet ((direction-flag (direction) + (ecase direction + ((:input :probe) sb-posix:o-rdonly) + ((:output :io) sb-posix:o-rdwr)))) + (let* ((truepath (probe-file path)) + (alien (cond + (truepath + (ecase direction + ((:input :probe) + (%%open path (direction-flag direction))) + ((:output :io) + (case if-exists + (:error (error "database already exists: ~S" path)) + (:append (%open path (direction-flag direction))) + ;; FIXME: not the best implementation of + ;; :SUPERSEDE semantics ever. + (:supersede (delete-file path) (%create path 0 0 0)))))) + ((eql direction :input) + (error "database does not exist: ~S" path)) + (t (%create path 0 0 0))))) + (cond + ((null-alien alien) + (case direction + (:probe nil) + (t (error "opening database failed: ~S" path)))) + (t (make-instance adb-class 'alien alien)))))) +(defmethod close ((o adb)) + (when (and (slot-boundp o 'alien) + (not (null-alien (slot-value o 'alien)))) + (%close (slot-value o 'alien)) + (sb-ext:cancel-finalization o) + (slot-makunbound o 'alien))) +(defmacro with-adb ((adb path &rest open-args &key direction adb-class if-exists) + &body body) + (declare (ignore direction adb-class if-exists)) + `(let ((,adb (open ,path ,@open-args))) + (unwind-protect + (locally ,@body) + (close ,adb)))) + +(defgeneric l2norm (db)) +(defmethod l2norm ((db adb)) + (%l2norm (slot-value db 'alien))) + +(defstruct (datum + (:constructor %make-datum) + (:constructor + make-datum + (key %data &key times power + &aux (data + (make-array (list (length %data) (length (elt %data 0))) + :element-type 'double-float + :initial-contents %data))))) + (key (error "missing argument") :type string) + (data (error "missing argument") :type (simple-array double-float (* *))) + (times nil :type (or null (simple-array double-float))) + (power nil :type (or null (simple-array double-float)))) + +(defgeneric insert (datum db)) + +(defmethod insert ((datum datum) (db adb)) + (let* ((data (datum-data datum)) + (nvectors (array-dimension data 0)) + (dim (array-dimension data 1))) + (when (datum-times datum) + (unless (= (array-total-size (datum-times datum)) (* 2 nvectors)) + (error "dimension mismatch for times: ~S" datum))) + (when (datum-power datum) + (unless (= (array-total-size (datum-power datum)) nvectors) + (error "dimension mismatch for power: ~S" datum))) + (with-alien ((d adb-datum-t)) + (sb-sys:with-pinned-objects ((datum-data datum) + (datum-times datum) + (datum-power datum)) + (setf (slot d 'dim) dim) + (setf (slot d 'nvectors) nvectors) + (setf (slot d 'key) (datum-key datum)) + (setf (slot d 'data) + (sb-sys:vector-sap (sb-ext:array-storage-vector (datum-data datum)))) + (if (datum-times datum) + (setf (slot d 'times) (sb-sys:vector-sap (sb-ext:array-storage-vector (datum-times datum)))) + (setf (slot d 'times) nil)) + (if (datum-power datum) + (setf (slot d 'power) (sb-sys:vector-sap (sb-ext:array-storage-vector (datum-times datum)))) + (setf (slot d 'power) nil)) + (sb-int:with-float-traps-masked (:invalid) + (%insert-datum (slot-value db 'alien) (addr d))))))) + +(defstruct result + (key "" :type string) + (distance 0d0 :type double-float) + (qpos 0 :type (and unsigned-byte fixnum)) + (ipos 0 :type (and unsigned-byte fixnum))) + +;;; Hrm. To copy (from the malloc heap) or not to copy? Copying +;;; would make things generally easier, I guess, and we have to hope +;;; that the order of magnitude is not such that the copying causes +;;; pain. +(defclass results (sequence standard-object) + ()) +(defclass copied-query-results (results) + ((results :initarg results :accessor %copied-results))) +(defmethod print-object ((o copied-query-results) s) + (pprint-logical-block (s nil) + (print-unreadable-object (o s :type t) + (format s "(~D results):~2I~@:_" (length o)) + (sequence:dosequence (r o) + (pprint-pop) + (format s "~A ~6,3e ~D ~D~@:_" + (result-key r) (result-distance r) + (result-qpos r) (result-ipos r)))))) + +(defmethod sequence:length ((o copied-query-results)) + (length (%copied-results o))) +(defmethod sequence:elt ((o copied-query-results) index) + (elt (%copied-results o) index)) +(defmethod (setf sequence:elt) (new-value (o copied-query-results) index) + (setf (elt (%copied-results o) index) new-value)) +(defmethod sequence:make-sequence-like + ((o copied-query-results) length &rest args + &key initial-element initial-contents) + (declare (ignore initial-element initial-contents)) + (let ((vector (apply #'make-array length args))) + (make-instance 'copied-query-results 'results vector))) +(defmethod sequence:adjust-sequence + ((o copied-query-results) length &rest args + &key initial-element initial-contents) + (declare (ignore initial-element initial-contents)) + (let ((results (%copied-results o))) + (apply #'sequence:adjust-sequence results length args)) + o) + +(defclass proxied-query-results (results) + ((adb :initarg adb) + (spec :initarg spec) + (results :initarg results))) +(defmethod initialize-instance :after ((o proxied-query-results) &key) + (when (and (slot-boundp o 'results) + (not (null-alien o)) + (slot-boundp o 'spec)) + (with-slots (results spec adb) o + (flet ((results-finalizer () + (with-slots (alien) adb + (%free-query-results alien spec results)))) + (sb-ext:finalize o #'results-finalizer))))) + +(defgeneric query (datum db &key)) + +;;; FIXME: I don't like this way of generalizing the boilerplate; +;;; isn't there a nice functional way of doing this? +(macrolet + ((def (name datum-class &body qdatum-forms) + `(defmethod ,name ((datum ,datum-class) (db adb) &key + (sequence-length 1) (sequence-start 0) + exhaustivep accumulation distance + ;; FIXME: dubious historical defaults + (npoints 10) (ntracks 10) + + (radius nil radiusp) + (include-keys nil include-keys-p) + (exclude-keys nil exclude-keys-p)) + (unless (slot-boundp db 'alien) + (error "database ~S is closed" db)) + (with-alien ((qid adb-query-id-t) + (qparams adb-query-parameters-t) + (qrefine adb-query-refine-t) + (qdatum adb-datum-t)) + ,@qdatum-forms + (setf (slot qid 'datum) (addr qdatum)) + (setf (slot qid 'sequence-length) sequence-length) + (setf (slot qid 'sequence-start) sequence-start) + (setf (slot qid 'flags) (if exhaustivep 1 0)) + + (setf (slot qparams 'accumulation) + (ecase accumulation + (:db 1) + (:per-track 2) + (:one-to-one 3))) + (setf (slot qparams 'distance) + (ecase distance + (:dot-product 1) + (:euclidean-normed 2) + (:euclidean 3))) + (setf (slot qparams 'npoints) (or npoints 0)) + (setf (slot qparams 'ntracks) (or ntracks 0)) + + (let ((refine-flags 0)) + (when radiusp + (setf refine-flags (logior refine-flags 4)) + (setf (slot qrefine 'radius) (float radius 0d0))) + ;; FIXME: the freeing of the KEYS slot in these + ;; include/exclude keylists isn't interrupt-safe. + ;; + ;; FIXME: think quite hard about the behaviour of this + ;; when LENGTH is 0. + (when include-keys-p + (setf refine-flags (logior refine-flags 1)) + (let ((length (length include-keys))) + (setf (slot (slot qrefine 'include) 'nkeys) length) + (let ((keys (make-alien c-string length))) + (setf (slot (slot qrefine 'include) 'keys) keys) + (loop for key being the elements of include-keys + for i upfrom 0 + do (setf (deref keys i) key))))) + (when exclude-keys-p + (setf refine-flags (logior refine-flags 2)) + (let ((length (length exclude-keys))) + (setf (slot (slot qrefine 'exclude) 'nkeys) length) + (let ((keys (make-alien c-string length))) + (setf (slot (slot qrefine 'exclude) 'keys) keys) + (loop for key being the elements of exclude-keys + for i upfrom 0 + do (setf (deref keys i) key))))) + (setf (slot qrefine 'flags) refine-flags)) + (setf (slot qrefine 'hopsize) 1) + + ;; FIXME: hm, this possibly suggests that there's something + ;; a bit wrong with the C audioDB interface. The API + ;; currently exposed effectively requires either that all + ;; the processing of query results happens in the same + ;; dynamic extent as the call to audiodb_query_spec(), or + ;; that the adb_query_spec_t object is allocated on the + ;; heap. We need to think harder about whether the spec + ;; argument is really required (I think it probably isn't). + ;; + ;; meanwhile, here we're using it with dynamic extent anyway, so + ;; we could put it right back on the stack. + (let ((qspec (make-alien adb-query-spec-t))) + (unwind-protect + (progn + (setf (slot qspec 'qid) qid) + (setf (slot qspec 'params) qparams) + (setf (slot qspec 'refine) qrefine) + + (let ((results + (sb-int:with-float-traps-masked (:invalid) + (%query (slot-value db 'alien) qspec)))) + (flet ((collect-copied-results () + (let ((nresults (slot results 'nresults)) + (cresults (slot results 'results))) + (coerce + (loop for i below nresults + for r = (deref cresults i) + collect (make-result + :key (slot r 'key) + :distance (slot r 'dist) + :qpos (slot r 'qpos) + :ipos (slot r 'ipos))) + 'vector)))) + (unwind-protect + (make-instance 'copied-query-results + 'results (collect-copied-results)) + (%free-query-results (slot-value db 'alien) qspec results))))) + (when (logbitp 0 (slot (slot qspec 'refine) 'flags)) + (free-alien (slot (slot (slot qspec 'refine) 'include) 'keys))) + (when (logbitp 1 (slot (slot qspec 'refine) 'flags)) + (free-alien (slot (slot (slot qspec 'refine) 'exclude) 'keys))) + (free-alien qspec))))))) + (def query string (setf (slot qdatum 'key) datum)) + (def query datum + (setf (slot qdatum 'key) (datum-key datum)) + (setf (slot qdatum 'dim) (array-dimension (datum-data datum) 1)) + (setf (slot qdatum 'nvectors) (array-dimension (datum-data datum) 0)) + (setf (slot qdatum 'data) (sb-sys:vector-sap (sb-ext:array-storage-vector (datum-data datum)))) + (if (datum-times datum) + (setf (slot qdatum 'times) (sb-sys:vector-sap (sb-ext:array-storage-vector (datum-times datum)))) + (setf (slot qdatum 'times) nil)) + (if (datum-power datum) + (setf (slot qdatum 'power) (sb-sys:vector-sap (sb-ext:array-storage-vector (datum-times datum)))) + (setf (slot qdatum 'power) nil)))) + +#+test +(sb-adb:with-adb (db "/home/csr21/tmp/omras2-workshop/9.adb") + (sb-adb:query "KSA_CHARM_337" db :exhaustivep t :sequence-length 30 + :accumulation :per-track :distance :euclidean :npoints 1 :ntracks 20)) + +#+test +(sb-adb:with-adb (db "/home/csr21/tmp/omras2-workshop/9.adb") + (sb-adb:query "KSA_CHARM_337" db :sequence-start 20 :sequence-length 20 + :accumulation :per-track :distance :euclidean-normed + :npoints 10 :ntracks 1)) + +#+test +(sb-adb:with-adb (db "/home/csr21/tmp/omras2-workshop/9.adb") + (sb-adb:query "KSA_CHARM_337" db + :exhaustivep t :sequence-length 30 + :accumulation :per-track :distance :euclidean-normed + :npoints 2 :ntracks 10)) + +;;; only hacks and tests below +#| +(defun foo () + (let ((db (%open "/home/csr21/tmp/omras2-workshop/9.adb" sb-posix:o-rdonly))) + (unless (null-alien db) + (unwind-protect + (with-alien ((status adb-status-t)) + (%status db (addr status)) + (print (list (slot status 'dim) (slot status 'nfiles)))) + (%close db))))) + +(defun set-up-spec (spec qid qparams qrefine) + (declare (type (alien adb-query-parameters-t) qparams) + (type (alien adb-query-refine-t) qrefine) + (type (alien adb-query-id-t) qid) + (type (alien adb-query-spec-t) spec)) + (setf (slot spec 'refine) qrefine) + nil) + +(defun bar () + (let ((db (%open "/home/csr21/tmp/omras2-workshop/9.adb" sb-posix:o-rdonly))) + (unless (null-alien db) + (unwind-protect + (with-alien ((qid adb-query-id-t) + (qparams adb-query-parameters-t) + (qrefine adb-query-refine-t) + (qspec adb-query-spec-t) + (datum adb-datum-t)) + (setf (slot datum 'key) "KSA_CHARM_337") + (setf (slot datum 'data) (sap-alien (sb-sys:int-sap 0) (* double))) + + (setf (slot qid 'datum) (addr datum)) + (setf (slot qid 'sequence-length) 30) + (setf (slot qid 'flags) 1) ; ADB_QID_FLAG_EXHAUSTIVE + + (setf (slot qparams 'accumulation) 2) ; ADB_ACCUMULATION_PER_TRACK + (setf (slot qparams 'distance) 2) ; ADB_DISTANCE_EUCLIDEAN_NORMED + (setf (slot qparams 'npoints) 1) + (setf (slot qparams 'ntracks) 20) + + (setf (slot qrefine 'flags) 0) + (setf (slot qrefine 'hopsize) 1) + + (setf (slot qspec 'qid) qid) + (setf (slot qspec 'params) qparams) + (setf (slot qspec 'refine) qrefine) + (let ((results (%query db (addr qspec)))) + (unless (null-alien results) + (unwind-protect + (flet ((print-result (n) + (let ((result (deref (slot results 'results) n))) + (format t "~&~A ~F ~D ~D~%" + (slot result 'key) (slot result 'dist) + (slot result 'qpos) (slot result 'ipos))))) + (dotimes (i (slot results 'nresults)) + (print-result i))) + (%free-query-results db (addr qspec) results))))) + (%close db))))) +|#