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