Work-in-progress repo for ambisonics extensions for OM-SoX
Marlon Schumacher
5 days ago 4020656850c3f64875a927ae9687127c163d9099
sources/classes/sox-hoaencode.lisp
@@ -1,11 +1,13 @@
;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.
; Questions:
; 1) Is there a built-in (flatten lst)-function?
; 2) Implement up to which order? <=> CLI issues to be expected (e.g., maximum command character length)?
; 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)
@@ -34,8 +36,10 @@
    )
)
; Ambisonics
;  ### Ambisonics ###
(defun sox-hoaencode-sn3d-factor (order degree)
"This is a placeholder for documentation"
    (ecase order
        (0 1)
        (1 1)
@@ -57,6 +61,7 @@
)
(defun sox-hoaencode-azimuth-factor (degree theta_deg)
"This is a placeholder for documentation"
    (let* 
        (
            (theta (sox-hoaencode-deg-to-rad theta_deg))
@@ -69,6 +74,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))
@@ -96,13 +102,15 @@
        )
    ))
; Convenience functions
; #### Utility functions ####
; 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.
#|
(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.
@@ -128,10 +136,14 @@
                (t (error "Positions must consist of lists of length 2 (ae) or 3 (xyz)."))
            ))
        (t (error "Positions must be of type 3dc or list."))))
|#
; High-level API
; Returns the gain value (linear, -1 to 1) for a single ACN-channel
; ####### 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, 
@@ -146,13 +158,18 @@
            (sox-hoaencode-elevation-factor order degree elevation_deg)))
    )
; Returns the gain values for all components at a specific order
; (sox-hoaencode-gain-single-component 1 1 45 0)
; 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)))
; Returns the gain values for all components up to a specific order
(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 
@@ -161,11 +178,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*)
     (gain :accessor gain :initarg :gain :initform 0 :documentation "gain value in dB (decibel)")
     (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, ...).")
)
@@ -173,6 +192,8 @@
(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)
    )
)