Work-in-progress repo for ambisonics extensions for OM-SoX
Alexander Nguyen
16 hours ago d6bb375789821f4e4e175465f50e77e8a6b1aaf7
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
e43e60 41 (defun sox-hoaencode-sn3d-factor (order degree)
MS 42 "This is a placeholder for documentation"
791513 43     (ecase order
AN 44         (0 1)
45         (1 1)
46         (2 
47             (ecase (abs degree)
48                 (0 1)
d6bb37 49                 (1 (/ (sqrt 3) 3))
AN 50                 (2 (/ (sqrt 3) 6))
791513 51             ))
AN 52         (3 
53             (ecase (abs degree)
54                 (0 1)
d6bb37 55                 (1 (/ (sqrt 6) 6))
AN 56                 (2 (/ (sqrt 15) 30))
57                 (3 (/ (sqrt 10) 60))
58             )
59         )
60         (4 
61             (ecase (abs degree)
62                 (0 1)
63                 (1 (/ (sqrt 10) 10))
64                 (2 (/ (sqrt 5) 30))
65                 (3 (/ (sqrt 70) 420))
66                 (4 (/ (sqrt 35) 840))
67             )
68         )
69         (5
70             (ecase (abs degree)
71                 (0 1)
72                 (1 (/ (sqrt 15) 15))
73                 (2 (/ (sqrt 105) 210))
74                 (3 (/ (sqrt 70) 840))
75                 (4 (/ (sqrt 35) 2520))
76                 (5 (/ (sqrt 14) 5040))
77             )
78         )
79         (6
80             (ecase (abs degree)
81                 (0 1)
82                 (1 (/ (sqrt 21) 21))
83                 (2 (/ (sqrt 210) 420))
84                 (3 (/ (sqrt 210) 2520))
85                 (4 (/ (sqrt 7) 2520))
86                 (5 (/ (sqrt 154) 55440))
87                 (6 (/ (sqrt 462) 332640))
88             )
89         )
90         (7
91             (ecase (abs degree)
92                 (0 1)
93                 (1 (/ (sqrt 7) 14))
94                 (2 (/ (sqrt 42) 252))
95                 (3 (/ (sqrt 21) 1260))
96                 (4 (/ (sqrt 231) 27720))
97                 (5 (/ (sqrt 231) 166320))
98                 (6 (/ (sqrt 6006) 4324320))
99                 (7 (/ (sqrt 429) 4324320))
791513 100             )
AN 101         )
102     )
103 )
104
105 (defun sox-hoaencode-azimuth-factor (degree theta_deg)
e43e60 106 "This is a placeholder for documentation"
791513 107     (let* 
AN 108         (
109             (theta (sox-hoaencode-deg-to-rad theta_deg))
110         )
111         (if (< degree 0) 
112             (sin (* (abs degree) theta)) 
113             (cos (* (abs degree) theta))
114         )
115     )
116 )
117
118 (defun sox-hoaencode-elevation-factor (order degree phi_deg)
e43e60 119 "This is a placeholder for documentation"
791513 120     (let* 
AN 121         (
122             (phi (sox-hoaencode-deg-to-rad phi_deg))
123         )
124         (ecase order
125             (0 1)
126             (1 
127                 (ecase (abs degree)
128                     (0 (sin phi))
129                     (1 (cos phi))
130                 ))
131             (2 
132                 (ecase (abs degree)
133                     (0 (- (/ (* 3 (expt (sin phi) 2)) 2) (/ 1 2)))
ff1d9f 134                     (1 (/ (* 3 (* (sin phi) 2)) 2))
791513 135                     (2 (* 3 (expt (cos phi) 2)))
AN 136                 ))
137             (3 
138                 (ecase (abs degree)
139                     (0 (/ (* (sin phi) (- (* 5 (expt (sin phi) 2)) 3)) 2))
140                     (1 (- (* 6 (cos phi)) (/ (* 15 (expt (cos phi) 3)) 2)))
141                     (2 (* -15 (sin phi) (- (expt (sin phi) 2) 1)))
142                     (3 (* 15 (expt (cos phi) 3)))
143                 ))
d6bb37 144             (4 
AN 145                 (ecase (abs degree)
146                     (0 (+ (/ (* 35 (expt (cos phi) 4)) 8) (- (* 5 (expt (cos phi) 2))) 1))
147                     (1 (- (/ (* (sin (* 2 phi)) (- (/ (* 35 (expt (cos phi) 2)) 2) 10)) 2)))
148                     (2 (- (/ (* (* 15 (expt (cos phi) 2)) (- (* 7 (expt (cos phi) 2)) 6)) 2)))
149                     (3 (* 105 (expt (cos phi) 3) (sin phi) ))
150                     (4 (* 105 (expt (cos phi) 4)))
151                 ))
152             (5 
153                 (ecase (abs degree)
154                     (0    (/ (*     (sin phi) (+ (- (* 63 (expt (sin phi) 4)) (* 70 (expt (sin phi) 2))) 15)) 8))
155                     (1    (/ (*  15 (sin phi) (+ (- (* 21 (expt (cos phi) 4)) (* 28 (expt (cos phi) 2)))  8)) 8))
156                     (2 (- (/ (* 105 (sin phi) (+ (- (*  3 (expt (sin phi) 4)) (*  4 (expt (sin phi) 2)))  1)) 2)))
157                     (3 (- (/ (* 105 (expt (cos phi) 3) (- (* 9 (expt (cos phi) 2)) 8))                        2)))
158                     (4       (* 945 (sin phi) (expt (- (expt (sin phi) 2) 1) 2)))
159                     (5       (* 945 (expt (cos phi) 5)))
160                 ))
161             (6 
162                 (ecase (abs degree)
163                     (0 (+ (- (/ (* 231 (expt (cos phi) 6)) 16)) (/ (* 189 (expt (cos phi) 4)) 8) (- (/ (* 21 (expt (cos phi) 2)) 2)) 1))
164                     (1 (/ (* 21 (sin (* 2 phi)) (+ (* 33 (expt (sin phi) 4)) (- (* 30 (expt (sin phi) 2))) 5)) 16))
165                     (2 (/ (* 105 (expt (cos phi) 2) (+ (* 33 (expt (cos phi) 4)) (- (* 48 (expt (cos phi) 2))) 16)) 8))
166                     (3 (- (/ (* 315 (expt (cos phi) 3) (sin phi)) (- (* 11 (expt (cos phi) 2)) 8)) 2))
167                     (4 (- (/ (* 945 (expt (cos phi) 4) (- (* 11 (expt (cos phi) 2)) 10)) 2)))
168                     (5 (* 10395 (expt (cos phi) 5) (sin phi)))
169                     (6 (* 10395 (expt (cos phi) 6)))
170                 ))
171             (7
172                 (ecase (abs degree)
173                     (0    (/ (* (sin phi) (+ (* 429 (expt (sin phi) 6)) (- (* 693 (expt (sin phi) 4))) (* 315 (expt (sin phi) 2)) (- 35))) 16))
174                     (1 (- (/ (* (* 7 (cos phi)) (+ (* 429 (expt (cos phi) 6)) (- (* 792 (expt (cos phi) 4))) (* 432 (expt (cos phi) 2)) (- 64))) 16)))
175                     (2 (- (/ (* 63 (sin phi) (- (expt (sin phi) 2) 1) (+ (* 143 (expt (sin phi) 4)) (- (* 110 (expt (sin phi) 2))) 15)) 8)))
176                     (3    (/ (* (* 315 (expt (cos phi) 3)) (+ (* 143 (expt (cos phi) 4)) (- (* 220 (expt (cos phi) 2))) 80)) 8))
177                     (4    (/ (* 3465 (sin phi) (- (* 13 (expt (sin phi) 2)) 3) (expt (- (expt (sin phi) 2) 1) 2)) 2))
178                     (5 (- (/ (* 10395 (expt (cos phi) 5) (- (* 13 (expt (cos phi) 2)) 12)) 2)))
179                     (6 (* -135135 (sin phi) (expt (- (expt (sin phi) 2) 1) 3)))
180                     (7 (* 135135 (expt (cos phi) 7)))
181                 ))
791513 182         )
AN 183     ))
184
197ce0 185
MS 186
187 ; ####### High-level API ########## 
188
189
791513 190 (defun sox-hoaencode-gain-single-component (order degree azimuth_deg elevation_deg)
197ce0 191   "Returns the gain value (linear, -1 to 1) for a single ACN-channel"
791513 192     (let
AN 193         (
194             ; It is assumed that azimuth_deg follows the implementation details of SpatDIF, 
195             ; where azimuth_deg runs counterclockwise. 
196             ; However, the formulas inside assume that azimuth_deg runs clockwise, i.e. 
197             ; the sign of azimuth_deg must be inverted.
198             (azimuth_deg_inverted (* -1 azimuth_deg))
199         )
200         (* 
201             (sox-hoaencode-sn3d-factor order degree) 
202             (sox-hoaencode-azimuth-factor degree azimuth_deg_inverted) 
203             (sox-hoaencode-elevation-factor order degree elevation_deg)))
204     )
205
197ce0 206 ; (sox-hoaencode-gain-single-component 1 1 45 0)
6ae8ed 207 ; @AN: what is the difference between "order" and "degree"?
197ce0 208
MS 209
791513 210 (defun sox-hoaencode-gains-by-order (order azimuth_deg elevation_deg)
197ce0 211   "Returns the gain values for all components at a specific order"
791513 212     (loop for degree from (* -1 order) to order collect 
AN 213         (sox-hoaencode-gain-single-component order degree azimuth_deg elevation_deg)))
214
197ce0 215
791513 216 (defun sox-hoaencode-gains-up-to-order (order azimuth_deg elevation_deg)
197ce0 217   "Returns the gain values for all components up to a specific order"
791513 218     (sox-hoaencode-double-to-float 
AN 219         (flatten
220             (loop for ord from 0 to order collect 
221                 (sox-hoaencode-gains-by-order ord azimuth_deg elevation_deg)))))
222
223
224 (defclass! sox-hoaencode (sox-input)
225     (
6ae8ed 226      (gains :accessor gains :initarg :gains :initform nil :documentation *sox-gain-doc*)
e43e60 227      (azimuth :accessor azimuth :initarg :azimuth :initform 0 :documentation "azimuth angle in degrees")
MS 228      (elevation :accessor elevation :initarg :elevation :initform 0 :documentation "elevation angle in degrees")
229      (order :accessor order :initarg :order :initform 3 :documentation *sox-hoaencode-order-doc*)
791513 230     )
AN 231     (:icon 100)
124c93 232     (:documentation "Sox-hoaencode encodes <sound> into a <order>-th ambisonic (HOA) signal at <positions>.
791513 233
AN 234     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, ...).")
235 )
236
237 (defmethod initialize-instance :after ((self sox-hoaencode) &rest l)
238     (declare (ignore l))
239     (when (sound self)
b36c86 240         (sox-init-sound self 'atom)
791513 241     )
AN 242 )
197ce0 243
MS 244