Work-in-progress repo for ambisonics extensions for OM-SoX
Marlon Schumacher
9 days ago 3312b1b854e0dd50ab11b7d648e37fddebfb0479
commit | author | age
b36c86 1 ;Authors: 
MS 2 ; A. Nguyen, 2025
3 ; M. Schumacher, 2025
791513 4
AN 5 ; Design limitations:
6 ; 1) Sounds must have identical sample rate; otherwise, SoX fails silently, literally.
7
8 ; Questions:
197ce0 9 ; 1) Is there a built-in (flatten lst)-function? Yes, om:flat
MS 10 ; 2) Implement up to which order? <=> CLI issues to be expected (e.g., maximum command character length)? max character length for CLI should be changeable 
791513 11
AN 12 (in-package :om)
13
14 ;;; SOX-HOAENCODE ========================
15
16 ; Util
17 (defun flatten (structure)
18     (cond 
19         ((null structure) 
20             nil)
21         ((atom structure) 
22             (list structure))
23         (t 
24             (mapcan #'flatten structure))
25     )
26 )
27
28 (defun sox-hoaencode-deg-to-rad (x)
29     (* (/ x 180) pi)
30 )
31
32 (defun sox-hoaencode-double-to-float (lst)
33     (mapcar 
34         (lambda (x) (float x 0.0S0)) 
35         lst
36     )
37 )
38
197ce0 39 ;  ### Ambisonics ###
MS 40
41 (defun sox-hoaencode-sn3d-factor (order degree) ; andere Nomenklatur, "degree" wäre besser "order" um Verwechslung mit Winkeln zu vermeiden
791513 42     (ecase order
AN 43         (0 1)
44         (1 1)
45         (2 
46             (ecase (abs degree)
47                 (2 (/ (sqrt 3) 6))
48                 (1 (/ (sqrt 3) 3))
49                 (0 1)
50             ))
51         (3 
52             (ecase (abs degree)
53                 (3 (/ (sqrt 10) 60))
54                 (2 (/ (sqrt 15) 30))
55                 (1 (/ (sqrt 6) 6))
56                 (0 1)
57             )
58         )
59     )
60 )
61
62 (defun sox-hoaencode-azimuth-factor (degree theta_deg)
63     (let* 
64         (
65             (theta (sox-hoaencode-deg-to-rad theta_deg))
66         )
67         (if (< degree 0) 
68             (sin (* (abs degree) theta)) 
69             (cos (* (abs degree) theta))
70         )
71     )
72 )
73
74 (defun sox-hoaencode-elevation-factor (order degree phi_deg)
75     (let* 
76         (
77             (phi (sox-hoaencode-deg-to-rad phi_deg))
78         )
79         (ecase order
80             (0 1)
81             (1 
82                 (ecase (abs degree)
83                     (0 (sin phi))
84                     (1 (cos phi))
85                 ))
86             (2 
87                 (ecase (abs degree)
88                     (0 (- (/ (* 3 (expt (sin phi) 2)) 2) (/ 1 2)))
89                     (1 (/ (* 3 (expt (sin phi) 2)) 2))
90                     (2 (* 3 (expt (cos phi) 2)))
91                 ))
92             (3 
93                 (ecase (abs degree)
94                     (0 (/ (* (sin phi) (- (* 5 (expt (sin phi) 2)) 3)) 2))
95                     (1 (- (* 6 (cos phi)) (/ (* 15 (expt (cos phi) 3)) 2)))
96                     (2 (* -15 (sin phi) (- (expt (sin phi) 2) 1)))
97                     (3 (* 15 (expt (cos phi) 3)))
98                 ))
99         )
100     ))
101
197ce0 102 ; #### Utility functions ####
791513 103
AN 104 (defun sox-hoaencode-auto-convert-positions (positions)
197ce0 105 "Ensures that positions are given as '((azimuth elevation) ...) (in degrees), i.e.
MS 106 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).
107 2) if list of list of two values, input is assumed to be '(azimuth elevation) coordinates and won't be transformed any further.
108 3) if 3dc, input is transformed to '(azimuth elevation) coordinates.
109 "
791513 110     (cond 
AN 111         ((subtypep (type-of positions) '3dc)  
112             (progn ; convert xyz->aed, keep azimuth and elevation only.
113                 (mat-trans 
114                     (butlast 
115                         (mat-trans 
116                             (mapcar 
117                                 (lambda (xyz) (multiple-value-list (om:xyz->aed (first xyz) (second xyz) (third xyz)))) 
118                                 (mat-trans (list (x-points positions) (y-points positions) (z-points positions)))
119                             ))))))
120         ((subtypep (type-of positions) 'list) 
121             (cond
122                 ((= 2 (length (first positions)))
123                     positions)
124                 ((= 3 (length (first positions))) 
125                     (mat-trans 
126                         (butlast 
127                             (mat-trans 
128                                 (mapcar 
129                                     (lambda (xyz) (multiple-value-list (om:xyz->aed (first xyz) (second xyz) (third xyz)))) 
130                                     positions
131                                 )))))
a590ad 132                 (t (error "Positions must consist of lists of length 2 (ae) or 3 (xyz)."))
791513 133             ))
a590ad 134         (t (error "Positions must be of type 3dc or list."))))
791513 135
197ce0 136
MS 137
138 ; ####### High-level API ########## 
139
140
791513 141 (defun sox-hoaencode-gain-single-component (order degree azimuth_deg elevation_deg)
197ce0 142   "Returns the gain value (linear, -1 to 1) for a single ACN-channel"
791513 143     (let
AN 144         (
145             ; It is assumed that azimuth_deg follows the implementation details of SpatDIF, 
146             ; where azimuth_deg runs counterclockwise. 
147             ; However, the formulas inside assume that azimuth_deg runs clockwise, i.e. 
148             ; the sign of azimuth_deg must be inverted.
149             (azimuth_deg_inverted (* -1 azimuth_deg))
150         )
151         (* 
152             (sox-hoaencode-sn3d-factor order degree) 
153             (sox-hoaencode-azimuth-factor degree azimuth_deg_inverted) 
154             (sox-hoaencode-elevation-factor order degree elevation_deg)))
155     )
156
197ce0 157 ; (sox-hoaencode-gain-single-component 1 1 45 0)
MS 158 ; what is the difference between "order" and "degree"?
159
160
791513 161 (defun sox-hoaencode-gains-by-order (order azimuth_deg elevation_deg)
197ce0 162   "Returns the gain values for all components at a specific order"
791513 163     (loop for degree from (* -1 order) to order collect 
AN 164         (sox-hoaencode-gain-single-component order degree azimuth_deg elevation_deg)))
165
197ce0 166
791513 167 (defun sox-hoaencode-gains-up-to-order (order azimuth_deg elevation_deg)
197ce0 168   "Returns the gain values for all components up to a specific order"
791513 169     (sox-hoaencode-double-to-float 
AN 170         (flatten
171             (loop for ord from 0 to order collect 
172                 (sox-hoaencode-gains-by-order ord azimuth_deg elevation_deg)))))
173
174
175 (defclass! sox-hoaencode (sox-input)
176     (
124c93 177         (positions :accessor positions :initarg :positions :initform '(0 0) :documentation *sox-hoaencode-positions-doc*)
MS 178         (order :accessor order :initarg :order :initform 3 :documentation *sox-hoaencode-order-doc*)
791513 179     )
AN 180     (:icon 100)
124c93 181     (:documentation "Sox-hoaencode encodes <sound> into a <order>-th ambisonic (HOA) signal at <positions>.
791513 182
AN 183     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 )
185
186 (defmethod initialize-instance :after ((self sox-hoaencode) &rest l)
187     (declare (ignore l))
188     (when (sound self)
b36c86 189         (sox-init-sound self 'atom)
791513 190     )
AN 191 )
197ce0 192
MS 193