Work-in-progress repo for ambisonics extensions for OM-SoX
Marlon Schumacher
3 days ago 27d7ae0a3f50b5554d4ead0fbb09c2490d62ad07
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                 (2 (/ (sqrt 3) 6))
49                 (1 (/ (sqrt 3) 3))
50                 (0 1)
51             ))
52         (3 
53             (ecase (abs degree)
54                 (3 (/ (sqrt 10) 60))
55                 (2 (/ (sqrt 15) 30))
56                 (1 (/ (sqrt 6) 6))
57                 (0 1)
58             )
59         )
60     )
61 )
62
63 (defun sox-hoaencode-azimuth-factor (degree theta_deg)
e43e60 64 "This is a placeholder for documentation"
791513 65     (let* 
AN 66         (
67             (theta (sox-hoaencode-deg-to-rad theta_deg))
68         )
69         (if (< degree 0) 
70             (sin (* (abs degree) theta)) 
71             (cos (* (abs degree) theta))
72         )
73     )
74 )
75
76 (defun sox-hoaencode-elevation-factor (order degree phi_deg)
e43e60 77 "This is a placeholder for documentation"
791513 78     (let* 
AN 79         (
80             (phi (sox-hoaencode-deg-to-rad phi_deg))
81         )
82         (ecase order
83             (0 1)
84             (1 
85                 (ecase (abs degree)
86                     (0 (sin phi))
87                     (1 (cos phi))
88                 ))
89             (2 
90                 (ecase (abs degree)
91                     (0 (- (/ (* 3 (expt (sin phi) 2)) 2) (/ 1 2)))
92                     (1 (/ (* 3 (expt (sin phi) 2)) 2))
93                     (2 (* 3 (expt (cos phi) 2)))
94                 ))
95             (3 
96                 (ecase (abs degree)
97                     (0 (/ (* (sin phi) (- (* 5 (expt (sin phi) 2)) 3)) 2))
98                     (1 (- (* 6 (cos phi)) (/ (* 15 (expt (cos phi) 3)) 2)))
99                     (2 (* -15 (sin phi) (- (expt (sin phi) 2) 1)))
100                     (3 (* 15 (expt (cos phi) 3)))
101                 ))
102         )
103     ))
104
197ce0 105
MS 106
107 ; ####### High-level API ########## 
108
109
791513 110 (defun sox-hoaencode-gain-single-component (order degree azimuth_deg elevation_deg)
197ce0 111   "Returns the gain value (linear, -1 to 1) for a single ACN-channel"
791513 112     (let
AN 113         (
114             ; It is assumed that azimuth_deg follows the implementation details of SpatDIF, 
115             ; where azimuth_deg runs counterclockwise. 
116             ; However, the formulas inside assume that azimuth_deg runs clockwise, i.e. 
117             ; the sign of azimuth_deg must be inverted.
118             (azimuth_deg_inverted (* -1 azimuth_deg))
119         )
120         (* 
121             (sox-hoaencode-sn3d-factor order degree) 
122             (sox-hoaencode-azimuth-factor degree azimuth_deg_inverted) 
123             (sox-hoaencode-elevation-factor order degree elevation_deg)))
124     )
125
197ce0 126 ; (sox-hoaencode-gain-single-component 1 1 45 0)
6ae8ed 127 ; @AN: what is the difference between "order" and "degree"?
197ce0 128
MS 129
791513 130 (defun sox-hoaencode-gains-by-order (order azimuth_deg elevation_deg)
197ce0 131   "Returns the gain values for all components at a specific order"
791513 132     (loop for degree from (* -1 order) to order collect 
AN 133         (sox-hoaencode-gain-single-component order degree azimuth_deg elevation_deg)))
134
197ce0 135
791513 136 (defun sox-hoaencode-gains-up-to-order (order azimuth_deg elevation_deg)
197ce0 137   "Returns the gain values for all components up to a specific order"
791513 138     (sox-hoaencode-double-to-float 
AN 139         (flatten
140             (loop for ord from 0 to order collect 
141                 (sox-hoaencode-gains-by-order ord azimuth_deg elevation_deg)))))
142
143
144 (defclass! sox-hoaencode (sox-input)
145     (
6ae8ed 146      (gains :accessor gains :initarg :gains :initform nil :documentation *sox-gain-doc*)
e43e60 147      (azimuth :accessor azimuth :initarg :azimuth :initform 0 :documentation "azimuth angle in degrees")
MS 148      (elevation :accessor elevation :initarg :elevation :initform 0 :documentation "elevation angle in degrees")
149      (order :accessor order :initarg :order :initform 3 :documentation *sox-hoaencode-order-doc*)
791513 150     )
AN 151     (:icon 100)
124c93 152     (:documentation "Sox-hoaencode encodes <sound> into a <order>-th ambisonic (HOA) signal at <positions>.
791513 153
AN 154     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, ...).")
155 )
156
157 (defmethod initialize-instance :after ((self sox-hoaencode) &rest l)
158     (declare (ignore l))
159     (when (sound self)
b36c86 160         (sox-init-sound self 'atom)
791513 161     )
AN 162 )
197ce0 163
MS 164