Skip to content

Instantly share code, notes, and snippets.

@joslloand
Created June 24, 2022 00:40
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/2de50f8c32ab065b7c6e31c8e9826637 to your computer and use it in GitHub Desktop.
Save joslloand/2de50f8c32ab065b7c6e31c8e9826637 to your computer and use it in GitHub Desktop.
three FOA line array examples

// -------------------------------------- /*

  1. match energy across front stage ( 0 & pi/2)

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 */

// energy beam performs "best" ~k = \energy; // most "focus", super-cardioid

// design panto decoder ~pantoDecoder = FoaDecoderMatrix.newPanto(~pantoNumChannels, ~pantoOrientation, ~k);

// extract matrix ~pantoMatrix = ~pantoDecoder.matrix

// use dominance to match center / side gain (0, pi/2) ~centerGain = 3.0677817524435.neg / 4; // in dB - energy energy <-- ~dominanceMatrix = FoaXformerMatrix.newDominate(~centerGain).matrix; /* Discard Z from dominance matrix... ... as the ATK's FOA panto convention doesn't include Z, for efficiency purposes.

NOTE: the ATK's HOA convention does include Z! */ ~dominanceMatrix = ~dominanceMatrix.getSub(0, 0, 3, 3); ~pantoMatrix = ~pantoMatrix.mulMatrix(~dominanceMatrix);

// 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... for analysis

should match actual target array

the dominance gain selected above matches for the specified value below */ ~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);

/* the resulting decoder can be saved via -writeToFile */

/* analyze

... need to convert to HOA to use the HOA analysis tools */ ~foaMatrix = ~linearDecoder.matrix;

// append zeros for Z (that's what ATK's HOA convention expects!) ~foaMatrix = Matrix.with((~foaMatrix.asArray.flop ++ [ 0.0.dup(5) ]).flop);

// convert from foa to hoa1 ~order = 1; ~hoaMatrix = ~foaMatrix.mulMatrix(HoaMatrixDecoder.newFormat(\fuma, ~order).matrix);

// make HOA decoder for analysis ~hoaDecoder = HoaMatrixDecoder.newFromMatrix(~hoaMatrix, ~linearDirections, ~order);

// analyze ~analysisDirections = Array.series(180).degrad; // 1/2 way round (center front / center back) ~linearAnalysis = ~hoaDecoder.analyzeDirections(~analysisDirections); // test...

// plot ~linearAnalysis[\amp].ampdb.plot("%: amp".format(~k), minval: -40, maxval: 10); ~linearAnalysis[\energy].ampdb.plot("%: energy".format(~k), minval: -40, maxval: 10); ~linearAnalysis[\rE][\directions].raddeg.flop.first.plot("%: rE angle".format(~k), minval: 0, maxval: ~linearSpread.raddeg / 2); ~linearAnalysis[\rE][\magnitudes].plot("%: |rE|".format(~k), minval: 0, maxval: 1);

/* discussion

  • amp & energy performance good (fairly constant) across the front stage
  • imaging angle is max at source angle ~145deg, then rapidly returns to center
  • image remains relatively stable until ~145deg, then rapidly decreases */

// -------------------------------------- /* 2) match energy at front and back ( 0 & pi )

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 */

// energy beam performs "best" ~k = \energy; // most "focus", super-cardioid

// design panto decoder ~pantoDecoder = FoaDecoderMatrix.newPanto(~pantoNumChannels, ~pantoOrientation, ~k);

// extract matrix ~pantoMatrix = ~pantoDecoder.matrix

// use dominance to match match front / back gain (0 & pi) ~centerGain = 17.246673161123.neg / 4; // in dB - energy energy <-- ~dominanceMatrix = FoaXformerMatrix.newDominate(~centerGain).matrix; /* Discard Z from dominance matrix... ... as the ATK's FOA panto convention doesn't include Z, for efficiency purposes.

NOTE: the ATK's HOA convention does include Z! */ ~dominanceMatrix = ~dominanceMatrix.getSub(0, 0, 3, 3); ~pantoMatrix = ~pantoMatrix.mulMatrix(~dominanceMatrix);

// 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... for analysis

should match actual target array

the dominance gain selected above matches for the specified value below */ ~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);

/* the resulting decoder can be saved via -writeToFile */

/* analyze

... need to convert to HOA to use the HOA analysis tools */ ~foaMatrix = ~linearDecoder.matrix;

// append zeros for Z (that's what ATK's HOA convention expects!) ~foaMatrix = Matrix.with((~foaMatrix.asArray.flop ++ [ 0.0.dup(5) ]).flop);

// convert from foa to hoa1 ~order = 1; ~hoaMatrix = ~foaMatrix.mulMatrix(HoaMatrixDecoder.newFormat(\fuma, ~order).matrix);

// make HOA decoder for analysis ~hoaDecoder = HoaMatrixDecoder.newFromMatrix(~hoaMatrix, ~linearDirections, ~order);

// analyze ~analysisDirections = Array.series(180).degrad; // 1/2 way round (center front / center back) ~linearAnalysis = ~hoaDecoder.analyzeDirections(~analysisDirections); // test...

// plot ~linearAnalysis[\amp].ampdb.plot("%: amp".format(~k), minval: -40, maxval: 10); ~linearAnalysis[\energy].ampdb.plot("%: energy".format(~k), minval: -40, maxval: 10); ~linearAnalysis[\rE][\directions].raddeg.flop.first.plot("%: rE angle".format(~k), minval: 0, maxval: ~linearSpread.raddeg / 2); ~linearAnalysis[\rE][\magnitudes].plot("%: |rE|".format(~k), minval: 0, maxval: 1);

/* discussion

  • side has slightly higher energy than center
  • imaging angle is max at source angle ~120deg, then rapidly returns to center
  • image remains relatively stable until ~120deg, then rapidly decreases */

// -------------------------------------- /* 3) map pi/2 to max reproduced angle

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 */

// energy beam performs "best" ~k = \energy; // most "focus", super-cardioid

// design panto decoder ~pantoDecoder = FoaDecoderMatrix.newPanto(~pantoNumChannels, ~pantoOrientation, ~k);

// extract matrix ~pantoMatrix = ~pantoDecoder.matrix

// use match max imaging angles ~zoomAngle = (143 - 90).degrad.neg; // energy max rE angle <-- ~zoomMatrix = FoaXformerMatrix.newZoom(~zoomAngle).matrix; /* Discard Z from dominance matrix... ... as the ATK's FOA panto convention doesn't include Z, for efficiency purposes.

NOTE: the ATK's HOA convention does include Z! */ ~zoomMatrix = ~zoomMatrix.getSub(0, 0, 3, 3); ~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... for analysis

should match actual target array

the dominance gain selected above matches for the specified value below */ ~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);

/* the resulting decoder can be saved via -writeToFile */

/* analyze

... need to convert to HOA to use the HOA analysis tools */ ~foaMatrix = ~linearDecoder.matrix;

// append zeros for Z (that's what ATK's HOA convention expects!) ~foaMatrix = Matrix.with((~foaMatrix.asArray.flop ++ [ 0.0.dup(5) ]).flop);

// convert from foa to hoa1 ~order = 1; ~hoaMatrix = ~foaMatrix.mulMatrix(HoaMatrixDecoder.newFormat(\fuma, ~order).matrix);

// make HOA decoder for analysis ~hoaDecoder = HoaMatrixDecoder.newFromMatrix(~hoaMatrix, ~linearDirections, ~order);

// analyze ~analysisDirections = Array.series(180).degrad; // 1/2 way round (center front / center back) ~linearAnalysis = ~hoaDecoder.analyzeDirections(~analysisDirections); // test...

// plot ~linearAnalysis[\amp].ampdb.plot("%: amp".format(~k), minval: -40, maxval: 10); ~linearAnalysis[\energy].ampdb.plot("%: energy".format(~k), minval: -40, maxval: 10); ~linearAnalysis[\rE][\directions].raddeg.flop.first.plot("%: rE angle".format(~k), minval: 0, maxval: ~linearSpread.raddeg / 2); ~linearAnalysis[\rE][\magnitudes].plot("%: |rE|".format(~k), minval: 0, maxval: 1);

/* discussion

  • gain is boosted for sources at 180deg (may not be desirable!!)
  • imaging angles increase rapidly towards max angle, then rapidly returns to center (may not be desirable!!)
  • max stability at max reproduced angle */
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment