;Authors:
|
; A. Nguyen, 2025
|
; M. Schumacher, 2025
|
|
; Design limitations:
|
; 1) Sounds must have identical sample rate; otherwise, SoX fails silently, literally.
|
|
; Questions:
|
; 1) Is there a built-in (flatten lst)-function? Yes, om:flat
|
; 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
|
|
(in-package :om)
|
|
;;; SOX-HOAENCODE ========================
|
|
; Util
|
(defun flatten (structure)
|
(cond
|
((null structure)
|
nil)
|
((atom structure)
|
(list structure))
|
(t
|
(mapcan #'flatten structure))
|
)
|
)
|
|
(defun sox-hoaencode-deg-to-rad (x)
|
(* (/ x 180) pi)
|
)
|
|
(defun sox-hoaencode-double-to-float (lst)
|
(mapcar
|
(lambda (x) (float x 0.0S0))
|
lst
|
)
|
)
|
|
; ### Ambisonics ###
|
|
(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)
|
))
|
(3
|
(ecase (abs degree)
|
(3 (/ (sqrt 10) 60))
|
(2 (/ (sqrt 15) 30))
|
(1 (/ (sqrt 6) 6))
|
(0 1)
|
)
|
)
|
)
|
)
|
|
(defun sox-hoaencode-azimuth-factor (degree theta_deg)
|
"This is a placeholder for documentation"
|
(let*
|
(
|
(theta (sox-hoaencode-deg-to-rad theta_deg))
|
)
|
(if (< degree 0)
|
(sin (* (abs degree) theta))
|
(cos (* (abs degree) theta))
|
)
|
)
|
)
|
|
(defun sox-hoaencode-elevation-factor (order degree phi_deg)
|
"This is a placeholder for documentation"
|
(let*
|
(
|
(phi (sox-hoaencode-deg-to-rad phi_deg))
|
)
|
(ecase order
|
(0 1)
|
(1
|
(ecase (abs degree)
|
(0 (sin phi))
|
(1 (cos phi))
|
))
|
(2
|
(ecase (abs degree)
|
(0 (- (/ (* 3 (expt (sin phi) 2)) 2) (/ 1 2)))
|
(1 (/ (* 3 (expt (sin phi) 2)) 2))
|
(2 (* 3 (expt (cos phi) 2)))
|
))
|
(3
|
(ecase (abs degree)
|
(0 (/ (* (sin phi) (- (* 5 (expt (sin phi) 2)) 3)) 2))
|
(1 (- (* 6 (cos phi)) (/ (* 15 (expt (cos phi) 3)) 2)))
|
(2 (* -15 (sin phi) (- (expt (sin phi) 2) 1)))
|
(3 (* 15 (expt (cos phi) 3)))
|
))
|
)
|
))
|
|
|
|
; ####### High-level API ##########
|
|
|
(defun sox-hoaencode-gain-single-component (order degree azimuth_deg elevation_deg)
|
"Returns the gain value (linear, -1 to 1) for a single ACN-channel"
|
(let
|
(
|
; It is assumed that azimuth_deg follows the implementation details of SpatDIF,
|
; where azimuth_deg runs counterclockwise.
|
; However, the formulas inside assume that azimuth_deg runs clockwise, i.e.
|
; the sign of azimuth_deg must be inverted.
|
(azimuth_deg_inverted (* -1 azimuth_deg))
|
)
|
(*
|
(sox-hoaencode-sn3d-factor order degree)
|
(sox-hoaencode-azimuth-factor degree azimuth_deg_inverted)
|
(sox-hoaencode-elevation-factor order degree elevation_deg)))
|
)
|
|
; (sox-hoaencode-gain-single-component 1 1 45 0)
|
; @AN: what is the difference between "order" and "degree"?
|
|
|
(defun sox-hoaencode-gains-by-order (order azimuth_deg elevation_deg)
|
"Returns the gain values for all components at a specific order"
|
(loop for degree from (* -1 order) to order collect
|
(sox-hoaencode-gain-single-component order degree azimuth_deg elevation_deg)))
|
|
|
(defun sox-hoaencode-gains-up-to-order (order azimuth_deg elevation_deg)
|
"Returns the gain values for all components up to a specific order"
|
(sox-hoaencode-double-to-float
|
(flatten
|
(loop for ord from 0 to order collect
|
(sox-hoaencode-gains-by-order ord azimuth_deg elevation_deg)))))
|
|
|
(defclass! sox-hoaencode (sox-input)
|
(
|
(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 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, ...).")
|
)
|
|
(defmethod initialize-instance :after ((self sox-hoaencode) &rest l)
|
(declare (ignore l))
|
(when (sound self)
|
(sox-init-sound self 'atom)
|
)
|
)
|