Skip to content

Instantly share code, notes, and snippets.

@joslloand
Created June 24, 2022 00:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save joslloand/e8db08e4e4a1f6f57e959ea4e1261dde to your computer and use it in GitHub Desktop.
Save joslloand/e8db08e4e4a1f6f57e959ea4e1261dde to your computer and use it in GitHub Desktop.
HOA line array example

/* custom five channel "line array" decoder / ~pantoNumChannels = 8; ~pantoOrientation = \point; // need a center speaker / for discussion on decoder k, see this:

https://depts.washington.edu/dxscdoc/Help/Classes/FoaDecoderMatrix.html#Decoder%20k */ // ~k = \velocity; // strict soundfield / planewave, hyper-cardioid // ~k = \energy; // most "focus", super-cardioid // ~k = \controlled; // most "spread", cardioid

~beamShape = \basic; ~beamShape = \energy; ~beamShape = \controlled;

// design panto decoder // ~pantoDecoder = FoaDecoderMatrix.newPanto(~pantoNumChannels, ~pantoOrientation, ~k); ~pantoDecoder = HoaMatrixDecoder.newPanto(~pantoNumChannels, ~pantoOrientation, ~beamShape, order: 1); // hoa test

// extract matrix ~pantoMatrix = ~pantoDecoder.matrix

// match front / back gain ~centerGain = 0.0; // in dB // ~centerGain = 35.12355809129.neg / 2; // in dB - basic amp ~centerGain = 16.613902125303.neg / 4; // in dB - basic energy ~centerGain = 17.246673161123.neg / 4; // in dB - energy energy <-- ~centerGain = 14.718893074894.neg / 4; // in dB - controlled energy ~dominanceMatrix = HoaMatrixXformer.newDominate(~centerGain, order: 1).matrix; ~pantoMatrix = ~pantoMatrix.mulMatrix(~dominanceMatrix);

// match center / side gain (0, pi/2) ~centerGain = 2.4099436857051.neg / 4; // in dB - basic energy ~centerGain = 3.0677817524435.neg / 4; // in dB - energy energy <-- ~centerGain = 3.3060535358798.neg / 4; // in dB - controlled energy ~dominanceMatrix = HoaMatrixXformer.newDominate(~centerGain, order: 1).matrix; ~pantoMatrix = ~pantoMatrix.mulMatrix(~dominanceMatrix);

// match distortion angles ~zoomAngle = 0.0; // in radians ~zoomAngle = (129 - 90).degrad.neg; // basic max rE angle // ~zoomAngle = (47 - 90).degrad.neg; // basic max |rE| ~zoomAngle = (143 - 90).degrad.neg; // energy max rE angle <-- // ~zoomAngle = (133 - 90).degrad.neg; // energy max |rE| ~zoomAngle = (139 - 90).degrad.neg; // controlled max rE angle // ~zoomAngle = (122 - 90).degrad.neg; // controlled max |rE| ~zoomMatrix = HoaMatrixXformer.newZoom(~zoomAngle, order: 1).matrix; ~pantoMatrix = ~pantoMatrix.mulMatrix(~zoomMatrix);

// design new truncated matrix /* channel ordering: [ farLeft, left, center, right, farRight ] */ ~linearMatrix = Matrix.with([ ~pantoMatrix.getRow(2), // farLeft ~pantoMatrix.getRow(1), // left ~pantoMatrix.getRow(0), // center ~pantoMatrix.getRow(7), // right ~pantoMatrix.getRow(6), // farRight ]);

// specify loudspeaker positions /* this step is really just a convenience...

we could use the directions from ~pantoDecoder, but a better idea would be to specify the expected target array */ ~linearNumChannels = 5; // ~linearSpread = 90.degrad; // in radians ~linearSpread = 120.degrad; // in radians ~linearDirections = Array.series(~linearNumChannels, ~linearSpread / 2, ~linearSpread.neg / (~linearNumChannels - 1));

// the new decoder!! // ~linearDecoder = FoaDecoderMatrix.newFromMatrix(~linearMatrix, ~linearDirections); ~linearDecoder = HoaMatrixDecoder.newFromMatrix(~linearMatrix, ~linearDirections, 1);

// analyze ~analysisDirections = ~pantoDecoder.directions.keep(3) ~analysisDirections = ~pantoDecoder.directions.keep(5) ~analysisDirections = ~pantoDecoder.directions ~analysisDirections = Array.series(180).degrad // 1/2 way round (center front / center back) ~analysisDirections = Array.series(90).degrad // 1/4 way round (center front / left)

~linearAnalysis = ~linearDecoder.analyzeDirections(~analysisDirections); // test...

~pantoDecoder.directions.keep(5).raddeg ~pantoDecoder.directions.keep(3).raddeg ~linearDecoder.directions.raddeg

~linearAnalysis[\amp].ampdb ~linearAnalysis[\rms].ampdb ~linearAnalysis[\energy].ampdb

~linearAnalysis[\spreadE][\cos]

~linearAnalysis[\rE][\magnitudes] ~linearAnalysis[\rE][\directions].raddeg ~linearAnalysis[\rE][\rVwarp].raddeg

~linearAnalysis[\rV][\magnitudes] ~linearAnalysis[\rV][\directions].raddeg

// plot ~linearAnalysis[\amp].ampdb.plot("%: amp".format(~beamShape), minval: -40, maxval: 10) ~linearAnalysis[\rms].ampdb.plot("%: rms".format(~beamShape), minval: -40, maxval: 10) ~linearAnalysis[\energy].ampdb.plot("%: energy".format(~beamShape), minval: -40, maxval: 10)

~linearAnalysis[\rE][\directions].raddeg.flop.first.plot("%: rE angle".format(~beamShape), minval: 0, maxval: ~linearSpread.raddeg / 2) ~linearAnalysis[\rE][\directions].raddeg.flop.first.maxItem ~linearAnalysis[\rE][\directions].raddeg.flop.first.maxIndex // degree

~linearAnalysis[\rE][\magnitudes].plot("%: |rE|".format(~beamShape), minval: 0, maxval: 1) ~linearAnalysis[\rE][\magnitudes].maxItem ~linearAnalysis[\rE][\magnitudes].maxIndex

/* gains - differences */ (~linearAnalysis[\amp].first / ~linearAnalysis[\amp].last).ampdb (~linearAnalysis[\rms].first / ~linearAnalysis[\rms].last).ampdb (~linearAnalysis[\energy].first / ~linearAnalysis[\energy].last).ampdb

(~linearAnalysis[\amp].maxItem / ~linearAnalysis[\amp].minItem).ampdb (~linearAnalysis[\rms].maxItem / ~linearAnalysis[\rms].minItem).ampdb (~linearAnalysis[\energy].maxItem / ~linearAnalysis[\energy].minItem).ampdb

/* offer:

  • energy, max angle
  • controlled, minimized energy difference */

// center front / center back

// basic -> 35.12355809129 // amp -> 16.613902125303 // rms, energy

// energy -> 14.492528873102 // amp -> 17.246673161123 // rms, energy

// controlled -> 9.1482137269319 // amp -> 14.718893074894 // rms, energy

// center front / left

// basic -> 5.7251122988553 // amp -> 2.4099436857051 // rms, energy

// energy -> 4.4179705534752 // amp -> 3.0677817524435 // rms, energy

// controlled -> 3.3490144452487 // amp -> 3.3060535358798 // rms, energy

/* probably best to match max angle.. */

// basic -> 45.154128659213 // max angle -> 129 // max angle index -> 0.89898831051693 // max rE -> 47 // max rE index

// energy -> 52.608724220529 // max angle -> 143 // max angle index -> 0.95018622556457 // max rE -> 133 // max rE index

// controlled -> 50.375349877527 // max angle -> 139 // max angle index -> 0.95125105218319 // max rE -> 122 // max rE index

~linearAnalysis[\rV][\magnitudes] ~linearAnalysis[\rV][\directions].raddeg.raddeg.flop.first.plot("rV", minval: 0, maxval: ~linearSpread.raddeg / 2)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment