Work-in-progress repo for ambisonics extensions for OM-SoX
Alexander Nguyen
15 hours ago d6bb375789821f4e4e175465f50e77e8a6b1aaf7
sources/classes/sox-hoaencode.lisp
@@ -1,4 +1,6 @@
;Authors: A. Nguyen, 2025.
;Authors:
; A. Nguyen, 2025
; M. Schumacher, 2025
; Design limitations:
; 1) Sounds must have identical sample rate; otherwise, SoX fails silently, literally.
@@ -36,28 +38,72 @@
;  ### Ambisonics ###
(defun sox-hoaencode-sn3d-factor (order degree) ; andere Nomenklatur, "degree" wäre besser "order" um Verwechslung mit Winkeln zu vermeiden
(defun sox-hoaencode-sn3d-factor (order degree)
"This is a placeholder for documentation"
    (ecase order
        (0 1)
        (1 1)
        (2 
            (ecase (abs degree)
                (2 (/ (sqrt 3) 6))
                (1 (/ (sqrt 3) 3))
                (0 1)
                (1 (/ (sqrt 3) 3))
                (2 (/ (sqrt 3) 6))
            ))
        (3 
            (ecase (abs degree)
                (3 (/ (sqrt 10) 60))
                (2 (/ (sqrt 15) 30))
                (1 (/ (sqrt 6) 6))
                (0 1)
                (1 (/ (sqrt 6) 6))
                (2 (/ (sqrt 15) 30))
                (3 (/ (sqrt 10) 60))
            )
        )
        (4
            (ecase (abs degree)
                (0 1)
                (1 (/ (sqrt 10) 10))
                (2 (/ (sqrt 5) 30))
                (3 (/ (sqrt 70) 420))
                (4 (/ (sqrt 35) 840))
            )
        )
        (5
            (ecase (abs degree)
                (0 1)
                (1 (/ (sqrt 15) 15))
                (2 (/ (sqrt 105) 210))
                (3 (/ (sqrt 70) 840))
                (4 (/ (sqrt 35) 2520))
                (5 (/ (sqrt 14) 5040))
            )
        )
        (6
            (ecase (abs degree)
                (0 1)
                (1 (/ (sqrt 21) 21))
                (2 (/ (sqrt 210) 420))
                (3 (/ (sqrt 210) 2520))
                (4 (/ (sqrt 7) 2520))
                (5 (/ (sqrt 154) 55440))
                (6 (/ (sqrt 462) 332640))
            )
        )
        (7
            (ecase (abs degree)
                (0 1)
                (1 (/ (sqrt 7) 14))
                (2 (/ (sqrt 42) 252))
                (3 (/ (sqrt 21) 1260))
                (4 (/ (sqrt 231) 27720))
                (5 (/ (sqrt 231) 166320))
                (6 (/ (sqrt 6006) 4324320))
                (7 (/ (sqrt 429) 4324320))
            )
        )
    )
)
(defun sox-hoaencode-azimuth-factor (degree theta_deg)
"This is a placeholder for documentation"
    (let* 
        (
            (theta (sox-hoaencode-deg-to-rad theta_deg))
@@ -70,6 +116,7 @@
)
(defun sox-hoaencode-elevation-factor (order degree phi_deg)
"This is a placeholder for documentation"
    (let* 
        (
            (phi (sox-hoaencode-deg-to-rad phi_deg))
@@ -84,7 +131,7 @@
            (2 
                (ecase (abs degree)
                    (0 (- (/ (* 3 (expt (sin phi) 2)) 2) (/ 1 2)))
                    (1 (/ (* 3 (expt (sin phi) 2)) 2))
                    (1 (/ (* 3 (* (sin phi) 2)) 2))
                    (2 (* 3 (expt (cos phi) 2)))
                ))
            (3 
@@ -94,42 +141,46 @@
                    (2 (* -15 (sin phi) (- (expt (sin phi) 2) 1)))
                    (3 (* 15 (expt (cos phi) 3)))
                ))
            (4
                (ecase (abs degree)
                    (0 (+ (/ (* 35 (expt (cos phi) 4)) 8) (- (* 5 (expt (cos phi) 2))) 1))
                    (1 (- (/ (* (sin (* 2 phi)) (- (/ (* 35 (expt (cos phi) 2)) 2) 10)) 2)))
                    (2 (- (/ (* (* 15 (expt (cos phi) 2)) (- (* 7 (expt (cos phi) 2)) 6)) 2)))
                    (3 (* 105 (expt (cos phi) 3) (sin phi) ))
                    (4 (* 105 (expt (cos phi) 4)))
                ))
            (5
                (ecase (abs degree)
                    (0    (/ (*     (sin phi) (+ (- (* 63 (expt (sin phi) 4)) (* 70 (expt (sin phi) 2))) 15)) 8))
                    (1    (/ (*  15 (sin phi) (+ (- (* 21 (expt (cos phi) 4)) (* 28 (expt (cos phi) 2)))  8)) 8))
                    (2 (- (/ (* 105 (sin phi) (+ (- (*  3 (expt (sin phi) 4)) (*  4 (expt (sin phi) 2)))  1)) 2)))
                    (3 (- (/ (* 105 (expt (cos phi) 3) (- (* 9 (expt (cos phi) 2)) 8))                        2)))
                    (4       (* 945 (sin phi) (expt (- (expt (sin phi) 2) 1) 2)))
                    (5       (* 945 (expt (cos phi) 5)))
                ))
            (6
                (ecase (abs degree)
                    (0 (+ (- (/ (* 231 (expt (cos phi) 6)) 16)) (/ (* 189 (expt (cos phi) 4)) 8) (- (/ (* 21 (expt (cos phi) 2)) 2)) 1))
                    (1 (/ (* 21 (sin (* 2 phi)) (+ (* 33 (expt (sin phi) 4)) (- (* 30 (expt (sin phi) 2))) 5)) 16))
                    (2 (/ (* 105 (expt (cos phi) 2) (+ (* 33 (expt (cos phi) 4)) (- (* 48 (expt (cos phi) 2))) 16)) 8))
                    (3 (- (/ (* 315 (expt (cos phi) 3) (sin phi)) (- (* 11 (expt (cos phi) 2)) 8)) 2))
                    (4 (- (/ (* 945 (expt (cos phi) 4) (- (* 11 (expt (cos phi) 2)) 10)) 2)))
                    (5 (* 10395 (expt (cos phi) 5) (sin phi)))
                    (6 (* 10395 (expt (cos phi) 6)))
                ))
            (7
                (ecase (abs degree)
                    (0    (/ (* (sin phi) (+ (* 429 (expt (sin phi) 6)) (- (* 693 (expt (sin phi) 4))) (* 315 (expt (sin phi) 2)) (- 35))) 16))
                    (1 (- (/ (* (* 7 (cos phi)) (+ (* 429 (expt (cos phi) 6)) (- (* 792 (expt (cos phi) 4))) (* 432 (expt (cos phi) 2)) (- 64))) 16)))
                    (2 (- (/ (* 63 (sin phi) (- (expt (sin phi) 2) 1) (+ (* 143 (expt (sin phi) 4)) (- (* 110 (expt (sin phi) 2))) 15)) 8)))
                    (3    (/ (* (* 315 (expt (cos phi) 3)) (+ (* 143 (expt (cos phi) 4)) (- (* 220 (expt (cos phi) 2))) 80)) 8))
                    (4    (/ (* 3465 (sin phi) (- (* 13 (expt (sin phi) 2)) 3) (expt (- (expt (sin phi) 2) 1) 2)) 2))
                    (5 (- (/ (* 10395 (expt (cos phi) 5) (- (* 13 (expt (cos phi) 2)) 12)) 2)))
                    (6 (* -135135 (sin phi) (expt (- (expt (sin phi) 2) 1) 3)))
                    (7 (* 135135 (expt (cos phi) 7)))
                ))
        )
    ))
; #### Utility functions ####
(defun sox-hoaencode-auto-convert-positions (positions)
"Ensures that positions are given as '((azimuth elevation) ...) (in degrees), i.e.
1) if list of list of three values, input is assumed to be '(x y z) coordinates and will be transformed to '(azimuth elevation) coordinates (in the navigational spherical coordinate system).
2) if list of list of two values, input is assumed to be '(azimuth elevation) coordinates and won't be transformed any further.
3) if 3dc, input is transformed to '(azimuth elevation) coordinates.
"
    (cond
        ((subtypep (type-of positions) '3dc)
            (progn ; convert xyz->aed, keep azimuth and elevation only.
                (mat-trans
                    (butlast
                        (mat-trans
                            (mapcar
                                (lambda (xyz) (multiple-value-list (om:xyz->aed (first xyz) (second xyz) (third xyz))))
                                (mat-trans (list (x-points positions) (y-points positions) (z-points positions)))
                            ))))))
        ((subtypep (type-of positions) 'list)
            (cond
                ((= 2 (length (first positions)))
                    positions)
                ((= 3 (length (first positions)))
                    (mat-trans
                        (butlast
                            (mat-trans
                                (mapcar
                                    (lambda (xyz) (multiple-value-list (om:xyz->aed (first xyz) (second xyz) (third xyz))))
                                    positions
                                )))))
                (t (error "Positions must consist of lists of length 2 (ae) or 3 (xyz)."))
            ))
        (t (error "Positions must be of type 3dc or list."))))
@@ -153,7 +204,7 @@
    )
; (sox-hoaencode-gain-single-component 1 1 45 0)
; what is the difference between "order" and "degree"?
; @AN: what is the difference between "order" and "degree"?
(defun sox-hoaencode-gains-by-order (order azimuth_deg elevation_deg)
@@ -172,11 +223,13 @@
(defclass! sox-hoaencode (sox-input)
    (
        (positions :accessor positions :initarg :positions :initform nil :documentation *sox-hoaencode-positions-doc*)
        (order :accessor order :initarg :order :initform nil :documentation *sox-hoaencode-order-doc*)
     (gains :accessor gains :initarg :gains :initform nil :documentation *sox-gain-doc*)
     (azimuth :accessor azimuth :initarg :azimuth :initform 0 :documentation "azimuth angle in degrees")
     (elevation :accessor elevation :initarg :elevation :initform 0 :documentation "elevation angle in degrees")
     (order :accessor order :initarg :order :initform 3 :documentation *sox-hoaencode-order-doc*)
    )
    (:icon 100)
    (:documentation "Sox-hoaencode transforms <sound> into a <order>-th ambisonic (HOA) signal at <positions>.
    (:documentation "Sox-hoaencode encodes <sound> into a <order>-th ambisonic (HOA) signal at <positions>.
    The signal follows the ambiX convention, i.e. it uses SN3D normalization and ACN channel ordering. The resulting file has (<order>+1)^2 channels (order 0: 1 channel, order 1: 4 channels, order 2: 9 channels, order 3: 16 channels, ...).")
)
@@ -184,7 +237,7 @@
(defmethod initialize-instance :after ((self sox-hoaencode) &rest l)
    (declare (ignore l))
    (when (sound self)
        (sox-init-sound self 'list)
        (sox-init-sound self 'atom)
    )
)