View Javadoc
1   /*
2    * Copyright (C) 2016 Alberto Irurueta Carro (alberto@irurueta.com)
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *         http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package com.irurueta.numerical.signal.processing;
17  
18  /**
19   * Convolves a 1D signal with a 1D kernel.
20   */
21  public class Convolver1D {
22  
23      /**
24       * Default constant value to use during convolution if CONSTANT_EDGE method
25       * is being used for signal's edge extension.
26       */
27      public static final double DEFAULT_CONSTANT_VALUE = 0.0;
28  
29      /**
30       * Default position of kernel center.
31       */
32      public static final int DEFAULT_KERNEL_CENTER = 0;
33  
34      /**
35       * Default method to use for signal's edge extension.
36       */
37      public static final ConvolverEdgeMethod DEFAULT_EDGE_METHOD = ConvolverEdgeMethod.ZERO_EDGE;
38  
39      /**
40       * Signal to be convolved.
41       */
42      private double[] signal;
43  
44      /**
45       * Kernel to convolve the signal with.
46       */
47      private double[] kernel;
48  
49      /**
50       * Position of kernel center. Typical values are the start of kernel
51       * (position 0) or the kernel's center. For the former, the edge extension
52       * is applied only at the end of the resulting signal; for the latter, edge
53       * extension is applied symmetrically at the beginning and end of the
54       * resulting signal.
55       */
56      private int kernelCenter = DEFAULT_KERNEL_CENTER;
57  
58      /**
59       * Edge extension method to use during convolution when parts of the kernel
60       * are required to lie outside the signal's boundaries.
61       */
62      private ConvolverEdgeMethod edgeMethod = DEFAULT_EDGE_METHOD;
63  
64      /**
65       * Constant value to use during edge extension when CONSTANT_EDGE method is
66       * being used.
67       */
68      private double constantValue = DEFAULT_CONSTANT_VALUE;
69  
70      /**
71       * Listener in charge of attending events generated by this instance.
72       */
73      private Convolver1DListener listener;
74  
75      /**
76       * Constructor.
77       */
78      public Convolver1D() {
79      }
80  
81      /**
82       * Constructor.
83       *
84       * @param signal signal to be convolved.
85       * @param kernel kernel to convolve signal with.
86       */
87      public Convolver1D(final double[] signal, final double[] kernel) {
88          setSignal(signal);
89          setKernel(kernel);
90      }
91  
92      /**
93       * Gets signal to be convolved.
94       *
95       * @return signal to be convolved.
96       */
97      public double[] getSignal() {
98          return signal;
99      }
100 
101     /**
102      * Sets signal to be convolved.
103      *
104      * @param signal signal to be convolved.
105      */
106     public final void setSignal(final double[] signal) {
107         this.signal = signal;
108     }
109 
110     /**
111      * Gets kernel to convolve the signal with.
112      *
113      * @return kernel to convolve the signal with.
114      */
115     public double[] getKernel() {
116         return kernel;
117     }
118 
119     /**
120      * Sets kernel to convolve the signal with.
121      *
122      * @param kernel kernel to convolve the signal with.
123      */
124     public final void setKernel(final double[] kernel) {
125         this.kernel = kernel;
126     }
127 
128     /**
129      * Gets position of kernel center. Typical values are the start of kernel
130      * (position 0) or the kernel's center. For the former, the edge extension
131      * is applied only at the end of the resulting signal; for the latter, edge
132      * extension is applied symmetrically at the beginning and end of the
133      * resulting signal.
134      *
135      * @return position of kernel center.
136      */
137     public int getKernelCenter() {
138         return kernelCenter;
139     }
140 
141     /**
142      * Sets position of kernel center. Typical values are the start of kernel
143      * (position 0) or the kernel's center. For the former, the edge extension
144      * is applied only at the end of the resulting signal; for the latter, edge
145      * extension is applied symmetrically at the beginning and end of the
146      * resulting signal.
147      *
148      * @param kernelCenter position of kernel center.
149      * @throws IllegalArgumentException if provided position is negative.
150      */
151     public void setKernelCenter(final int kernelCenter) {
152         if (kernelCenter < 0) {
153             throw new IllegalArgumentException("kernel center must be zero or greater and within kernel's boundaries");
154         }
155 
156         this.kernelCenter = kernelCenter;
157     }
158 
159     /**
160      * Gets edge extension method to use during convolution when parts of the
161      * kernel are required to lie outside the signal's boundaries.
162      *
163      * @return edge extension method.
164      */
165     public ConvolverEdgeMethod getEdgeMethod() {
166         return edgeMethod;
167     }
168 
169     /**
170      * Sets edge extension method to use during convolution when parts of the
171      * kernel are required to lie outside the signal's boundaries.
172      *
173      * @param edgeMethod edge extension method.
174      */
175     public void setEdgeMethod(final ConvolverEdgeMethod edgeMethod) {
176         this.edgeMethod = edgeMethod;
177     }
178 
179     /**
180      * Gets constant value to use during edge extension when CONSTANT_EDGE
181      * method is being used.
182      *
183      * @return constant value to use during edge extension.
184      */
185     public double getConstantValue() {
186         return constantValue;
187     }
188 
189     /**
190      * Sets constant value to use during edge extension when CONSTANT_EDGE
191      * method is being used.
192      *
193      * @param constantValue constant value to use during edge extension.
194      */
195     public void setConstantValue(final double constantValue) {
196         this.constantValue = constantValue;
197     }
198 
199     /**
200      * Gets listener in charge of attending events generated by this instance.
201      *
202      * @return listener in charge of attending events generated by this
203      * instance.
204      */
205     public Convolver1DListener getListener() {
206         return listener;
207     }
208 
209     /**
210      * Sets listener in charge of attending events generated by this instance.
211      *
212      * @param listener listener in charge of attending events generated by this
213      *                 instance.
214      */
215     public void setListener(final Convolver1DListener listener) {
216         this.listener = listener;
217     }
218 
219     /**
220      * Indicates whether this instance is ready to start a convolution.
221      *
222      * @return true if ready, false otherwise.
223      */
224     public boolean isReady() {
225         return signal != null && kernel != null && kernelCenter >= 0 && kernelCenter < kernel.length;
226     }
227 
228     /**
229      * Convolves provided signal with provided kernel using provided
230      * configuration for edge extension.
231      *
232      * @return a new array containing result of convolution.
233      */
234     public double[] convolve() {
235         return convolve(signal, kernel, kernelCenter, edgeMethod, constantValue, listener);
236     }
237 
238     /**
239      * Convolves provided signal with provided kernel using provided
240      * configuration for edge extension.
241      *
242      * @param result array where result of convolution will be stored. Must
243      *               have the sum of the lengths of signal and kernel minus one.
244      * @throws IllegalArgumentException if provided result array does not have
245      *                                  proper length.
246      */
247     public void convolve(final double[] result) {
248         convolve(signal, kernel, kernelCenter, edgeMethod, constantValue, result, listener);
249     }
250 
251     /**
252      * Convolves provided signal with provided kernel assuming a zero value
253      * extension method and kernel center located at its origin.
254      *
255      * @param signal signal to be convolved.
256      * @param kernel kernel used for convolution.
257      * @return a new array containing result of convolution.
258      */
259     public static double[] convolve(final double[] signal, final double[] kernel) {
260         return convolve(signal, kernel, DEFAULT_KERNEL_CENTER, (Convolver1DListener) null);
261     }
262 
263     /**
264      * Convolves provided signal with provided kernel assuming a zero value
265      * extension method and kernel center located at its origin.
266      *
267      * @param signal   signal to be convolved.
268      * @param kernel   kernel used for convolution.
269      * @param listener listener attending events generated during convolution.
270      * @return a new array containing result of convolution.
271      */
272     public static double[] convolve(final double[] signal, final double[] kernel, final Convolver1DListener listener) {
273         return convolve(signal, kernel, DEFAULT_KERNEL_CENTER, listener);
274     }
275 
276     /**
277      * Convolves provided signal with provided kernel assuming a zero value
278      * extension method and kernel center located at its origin.
279      *
280      * @param signal signal to be convolved.
281      * @param kernel kernel used for convolution.
282      * @param result array where result of convolution will be stored. Must
283      *               have the sum of the lengths of signal and kernel minus one.
284      * @throws IllegalArgumentException if provided result array does not
285      *                                  have proper length.
286      */
287     public static void convolve(final double[] signal, final double[] kernel, final double[] result) {
288         convolve(signal, kernel, DEFAULT_KERNEL_CENTER, result, null);
289     }
290 
291     /**
292      * Convolves provided signal with provided kernel assuming a zero value
293      * extension method and kernel center located at its origin.
294      *
295      * @param signal   signal to be convolved.
296      * @param kernel   kernel used for convolution.
297      * @param result   array where result of convolution will be stored. Must
298      *                 have the sum of the lengths of signal and kernel minus one.
299      * @param listener listener attending events generated during convolution.
300      * @throws IllegalArgumentException if provided result array does not
301      *                                  have proper length.
302      */
303     public static void convolve(final double[] signal, final double[] kernel, final double[] result,
304                                 final Convolver1DListener listener) {
305         convolve(signal, kernel, DEFAULT_KERNEL_CENTER, result, listener);
306     }
307 
308     /**
309      * Convolves provided signal with provided kernel assuming a zero value
310      * extension method.
311      *
312      * @param signal       signal to be convolved.
313      * @param kernel       kernel used for convolution.
314      * @param kernelCenter position of kernel center. This determines how
315      *                     signal's edge extension is applied. Must be a value within kernel's
316      *                     boundaries.
317      * @return a new array containing result of convolution.
318      * @throws IllegalArgumentException if kernel center is not within kernel's
319      *                                  boundaries.
320      */
321     public static double[] convolve(final double[] signal, final double[] kernel, final int kernelCenter) {
322         return convolve(signal, kernel, kernelCenter, (Convolver1DListener) null);
323     }
324 
325     /**
326      * Convolves provided signal with provided kernel assuming a zero value
327      * extension method.
328      *
329      * @param signal       signal to be convolved.
330      * @param kernel       kernel used for convolution.
331      * @param kernelCenter position of kernel center. This determines how
332      *                     signal's edge extension is applied. Must be a value within kernel's
333      *                     boundaries.
334      * @param listener     listener attending events generated during convolution.
335      * @return a new array containing result of convolution.
336      * @throws IllegalArgumentException if kernel center is not within kernel's
337      *                                  boundaries.
338      */
339     public static double[] convolve(final double[] signal, final double[] kernel, final int kernelCenter,
340                                     final Convolver1DListener listener) {
341         return convolve(signal, kernel, kernelCenter, DEFAULT_EDGE_METHOD, listener);
342     }
343 
344     /**
345      * Convolves provided signal with provided kernel assuming a zero value
346      * extension method.
347      *
348      * @param signal       signal to be convolved.
349      * @param kernel       kernel used for convolution.
350      * @param kernelCenter position of kernel center. This determines how
351      *                     signal's edge extension is applied. Must be a value within kernel's
352      *                     boundaries.
353      * @param result       array where result of convolution will be stored. Must
354      *                     have the sum of the lengths of signal and kernel minus one.
355      * @throws IllegalArgumentException if provided result array does not
356      *                                  have proper length or if kernel center is not within kernel's boundaries.
357      */
358     public static void convolve(final double[] signal, final double[] kernel, final int kernelCenter,
359                                 final double[] result) {
360         convolve(signal, kernel, kernelCenter, result, null);
361     }
362 
363     /**
364      * Convolves provided signal with provided kernel assuming a zero value
365      * extension method.
366      *
367      * @param signal       signal to be convolved.
368      * @param kernel       kernel used for convolution.
369      * @param kernelCenter position of kernel center. This determines how
370      *                     signal's edge extension is applied. Must be a value within kernel's
371      *                     boundaries.
372      * @param result       array where result of convolution will be stored. Must
373      *                     have the sum of the lengths of signal and kernel minus one.
374      * @param listener     listener attending events generated during convolution.
375      * @throws IllegalArgumentException if provided result array does not
376      *                                  have proper length or if kernel center is not within kernel's boundaries.
377      */
378     public static void convolve(final double[] signal, final double[] kernel, final int kernelCenter,
379                                 final double[] result, final Convolver1DListener listener) {
380         convolve(signal, kernel, kernelCenter, DEFAULT_EDGE_METHOD, result, listener);
381     }
382 
383     /**
384      * Convolves provided signal with provided kernel assuming a zero value when
385      * constant edge extension is used (which makes it equivalent to zero
386      * extension method).
387      *
388      * @param signal       signal to be convolved.
389      * @param kernel       kernel used for convolution.
390      * @param kernelCenter position of kernel center. This determines how
391      *                     signal's edge extension is applied. Must be a value within kernel's
392      *                     boundaries.
393      * @param edgeMethod   edge extension method.
394      * @return a new array containing result of convolution.
395      * @throws IllegalArgumentException if kernel center is not within kernel's
396      *                                  boundaries.
397      */
398     public static double[] convolve(final double[] signal, final double[] kernel, final int kernelCenter,
399                                     final ConvolverEdgeMethod edgeMethod) {
400         return convolve(signal, kernel, kernelCenter, edgeMethod, (Convolver1DListener) null);
401     }
402 
403     /**
404      * Convolves provided signal with provided kernel assuming a zero value when
405      * constant edge extension is used (which makes it equivalent to zero
406      * extension method).
407      *
408      * @param signal       signal to be convolved.
409      * @param kernel       kernel used for convolution.
410      * @param kernelCenter position of kernel center. This determines how
411      *                     signal's edge extension is applied. Must be a value within kernel's
412      *                     boundaries.
413      * @param edgeMethod   edge extension method.
414      * @param listener     listener attending events generated during convolution.
415      * @return a new array containing result of convolution.
416      * @throws IllegalArgumentException if kernel center is not within kernel's
417      *                                  boundaries.
418      */
419     public static double[] convolve(final double[] signal, final double[] kernel, final int kernelCenter,
420                                     final ConvolverEdgeMethod edgeMethod, final Convolver1DListener listener) {
421         return convolve(signal, kernel, kernelCenter, edgeMethod, DEFAULT_CONSTANT_VALUE, listener);
422     }
423 
424     /**
425      * Convolves provided signal with provided kernel assuming a zero value when
426      * constant edge extension is used (which makes it equivalent to zero
427      * extension method).
428      *
429      * @param signal       signal to be convolved.
430      * @param kernel       kernel used for convolution.
431      * @param kernelCenter position of kernel center. This determines how
432      *                     signal's edge extension is applied. Must be a value within kernel's
433      *                     boundaries.
434      * @param edgeMethod   edge extension method.
435      * @param result       array where result of convolution will be stored. Must
436      *                     have the sum of the lengths of signal and kernel minus one.
437      * @throws IllegalArgumentException if provided result array does not
438      *                                  have proper length or if kernel center is not within kernel's boundaries.
439      */
440     public static void convolve(final double[] signal, final double[] kernel, final int kernelCenter,
441                                 final ConvolverEdgeMethod edgeMethod, final double[] result) {
442         convolve(signal, kernel, kernelCenter, edgeMethod, result, null);
443     }
444 
445     /**
446      * Convolves provided signal with provided kernel assuming a zero value when
447      * constant edge extension is used (which makes it equivalent to zero
448      * extension method).
449      *
450      * @param signal       signal to be convolved.
451      * @param kernel       kernel used for convolution.
452      * @param kernelCenter position of kernel center. This determines how
453      *                     signal's edge extension is applied. Must be a value within kernel's
454      *                     boundaries.
455      * @param edgeMethod   edge extension method.
456      * @param listener     listener attending events generated during convolution.
457      * @param result       array where result of convolution will be stored. Must
458      *                     have the sum of the lengths of signal and kernel minus one.
459      * @throws IllegalArgumentException if provided result array does not
460      *                                  have proper length or if kernel center is not within kernel's boundaries.
461      */
462     public static void convolve(final double[] signal, final double[] kernel, final int kernelCenter,
463                                 final ConvolverEdgeMethod edgeMethod, final double[] result,
464                                 final Convolver1DListener listener) {
465         convolve(signal, kernel, kernelCenter, edgeMethod, DEFAULT_CONSTANT_VALUE, result, listener);
466     }
467 
468     /**
469      * Convolves provided signal with provided kernel.
470      *
471      * @param signal        signal to be convolved.
472      * @param kernel        kernel used for convolution.
473      * @param kernelCenter  position of kernel center. This determines how
474      *                      signal's edge extension is applied. Must be a value within kernel's
475      *                      boundaries.
476      * @param edgeMethod    edge extension method.
477      * @param constantValue constant value to be used for edge extension if
478      *                      constant extension method is used.
479      * @return a new array containing result of convolution.
480      * @throws IllegalArgumentException if kernel center is not within kernel's
481      *                                  boundaries.
482      */
483     public static double[] convolve(final double[] signal, final double[] kernel, final int kernelCenter,
484                                     final ConvolverEdgeMethod edgeMethod, final double constantValue) {
485         return convolve(signal, kernel, kernelCenter, edgeMethod, constantValue, (Convolver1DListener) null);
486     }
487 
488     /**
489      * Convolves provided signal with provided kernel.
490      *
491      * @param signal        signal to be convolved.
492      * @param kernel        kernel used for convolution.
493      * @param kernelCenter  position of kernel center. This determines how
494      *                      signal's edge extension is applied. Must be a value within kernel's
495      *                      boundaries.
496      * @param edgeMethod    edge extension method.
497      * @param constantValue constant value to be used for edge extension if
498      *                      constant extension method is used.
499      * @param listener      listener attending events generated during convolution.
500      * @return a new array containing result of convolution.
501      * @throws IllegalArgumentException if kernel center is not within kernel's
502      *                                  boundaries.
503      */
504     public static double[] convolve(final double[] signal, final double[] kernel, final int kernelCenter,
505                                     final ConvolverEdgeMethod edgeMethod, final double constantValue,
506                                     final Convolver1DListener listener) {
507 
508         final var result = new double[signal.length + kernel.length - 1];
509         convolve(signal, kernel, kernelCenter, edgeMethod, constantValue, result, listener);
510         return result;
511     }
512 
513     /**
514      * Convolves provided signal with provided kernel.
515      *
516      * @param signal        signal to be convolved.
517      * @param kernel        kernel used for convolution.
518      * @param kernelCenter  position of kernel center. This determines how
519      *                      signal's edge extension is applied. Must be a value within kernel's
520      *                      boundaries.
521      * @param edgeMethod    edge extension method.
522      * @param constantValue constant value to be used for edge extension if
523      *                      constant extension method is used.
524      * @param result        array where result of convolution will be stored. Must
525      *                      have the sum of the lengths of signal and kernel minus one.
526      * @throws IllegalArgumentException if provided result array does not
527      *                                  have proper length or if kernel center is not within kernel's boundaries.
528      */
529     public static void convolve(final double[] signal, final double[] kernel, final int kernelCenter,
530                                 final ConvolverEdgeMethod edgeMethod, final double constantValue,
531                                 final double[] result) {
532         convolve(signal, kernel, kernelCenter, edgeMethod, constantValue, result, null);
533     }
534 
535     /**
536      * Convolves provided signal with provided kernel.
537      *
538      * @param signal        signal to be convolved.
539      * @param kernel        kernel used for convolution.
540      * @param kernelCenter  position of kernel center. This determines how
541      *                      signal's edge extension is applied. Must be a value within kernel's
542      *                      boundaries.
543      * @param edgeMethod    edge extension method.
544      * @param constantValue constant value to be used for edge extension if
545      *                      constant extension method is used.
546      * @param result        array where result of convolution will be stored. Must
547      *                      have the sum of the lengths of signal and kernel minus one.
548      * @param listener      listener attending events generated during convolution.
549      * @throws IllegalArgumentException if provided result array does not
550      *                                  have proper length or if kernel center is not within kernel's boundaries.
551      */
552     public static void convolve(final double[] signal, final double[] kernel, final int kernelCenter,
553                                 final ConvolverEdgeMethod edgeMethod, final double constantValue, final double[] result,
554                                 final Convolver1DListener listener) {
555         if (kernelCenter < 0 || kernelCenter >= kernel.length) {
556             throw new IllegalArgumentException("kernel center must be within kernel boundaries");
557         }
558         if (result.length != signal.length + kernel.length - 1) {
559             throw new IllegalArgumentException(
560                     " result array must have a length equal to the sum of signal and kernel lengths minus one");
561         }
562 
563         switch (edgeMethod) {
564             case MIRROR_EDGE:
565                 internalConvolveMirror(signal, kernel, kernelCenter, result, listener);
566                 break;
567             case REPEAT_EDGE:
568                 internalConvolveRepeat(signal, kernel, kernelCenter, result, listener);
569                 break;
570             case CONSTANT_EDGE:
571                 internalConvolveConstant(signal, kernel, kernelCenter, constantValue, result, listener);
572                 break;
573             case ZERO_EDGE:
574             default:
575                 internalConvolveZero(signal, kernel, kernelCenter, result, listener);
576                 break;
577         }
578     }
579 
580     /**
581      * Internal method to convolve signal using zero edge extension method.
582      *
583      * @param signal       signal to be convolved.
584      * @param kernel       kernel to convolve with.
585      * @param kernelCenter kernel center.
586      * @param result       array where result is stored.
587      * @param listener     listener attending events generated during convolution.
588      */
589     private static void internalConvolveZero(
590             final double[] signal, final double[] kernel, final int kernelCenter, final double[] result,
591             final Convolver1DListener listener) {
592         final var kernelLength = kernel.length;
593         final var resultLength = result.length;
594         double accum;
595         int signalPos;
596 
597         if (listener != null) {
598             listener.onStartConvolution();
599         }
600 
601         for (var i = 0; i < resultLength; i++) {
602 
603             accum = 0.0;
604             signalPos = i - kernelCenter;
605             for (var j = 0; j < kernelLength; j++) {
606                 final var signalValue = getSignalValueZero(signal, signalPos - j);
607                 accum += signalValue * kernel[j];
608             }
609 
610             result[i] = accum;
611 
612             if (listener != null) {
613                 final var progress = (float) i / (float) resultLength;
614                 listener.onConvolveProgressChange(progress);
615             }
616         }
617 
618         if (listener != null) {
619             listener.onFinishConvolution();
620         }
621     }
622 
623     /**
624      * Internal method to determine signal value even when a position outside
625      * its boundaries is requested when zero edge extension is being used.
626      *
627      * @param signal signal to request a value for.
628      * @param pos    requested position.
629      * @return signal value when position is within boundaries or zero
630      * otherwise.
631      */
632     protected static double getSignalValueZero(final double[] signal, final int pos) {
633         if (pos >= 0 && pos < signal.length) {
634             return signal[pos];
635         } else {
636             return 0.0;
637         }
638     }
639 
640     /**
641      * Internal method to convolve signal using constant value edge extension
642      * method.
643      *
644      * @param signal        signal to be convolved.
645      * @param kernel        kernel to convolve with.
646      * @param kernelCenter  kernel center.
647      * @param constantValue constant value to use for positions outside signal
648      *                      boundaries.
649      * @param result        array where result is stored.
650      * @param listener      listener attending events generated during convolution.
651      */
652     private static void internalConvolveConstant(
653             final double[] signal, final double[] kernel, final int kernelCenter, final double constantValue,
654             final double[] result, final Convolver1DListener listener) {
655         final var kernelLength = kernel.length;
656         final var resultLength = result.length;
657         double accum;
658         int signalPos;
659 
660         if (listener != null) {
661             listener.onStartConvolution();
662         }
663 
664         for (var i = 0; i < resultLength; i++) {
665 
666             accum = 0.0;
667             signalPos = i - kernelCenter;
668             for (var j = 0; j < kernelLength; j++) {
669                 final var signalValue = getSignalValueConstant(signal, signalPos - j, constantValue);
670                 accum += signalValue * kernel[j];
671             }
672 
673             result[i] = accum;
674 
675             if (listener != null) {
676                 final var progress = (float) i / (float) resultLength;
677                 listener.onConvolveProgressChange(progress);
678             }
679         }
680 
681         if (listener != null) {
682             listener.onFinishConvolution();
683         }
684     }
685 
686     /**
687      * Internal method to determine signal value even when a position outside
688      * its boundaries is requested for constant value edge extension.
689      *
690      * @param signal        signal to request a value for.
691      * @param pos           requested position.
692      * @param constantValue constant value to use when requesting a position
693      *                      outside signal boundaries.
694      * @return signal value when position is within boundaries or constant
695      * value otherwise.
696      */
697     protected static double getSignalValueConstant(final double[] signal, final int pos, final double constantValue) {
698         if (pos >= 0 && pos < signal.length) {
699             return signal[pos];
700         } else {
701             return constantValue;
702         }
703     }
704 
705     /**
706      * Internal method to convolve signal using a repeat extension method.
707      *
708      * @param signal       signal to be convolved.
709      * @param kernel       kernel to convolve with.
710      * @param kernelCenter kernel center.
711      * @param result       array where result is stored.
712      * @param listener     listener attending events generated during convolution.
713      */
714     private static void internalConvolveRepeat(
715             final double[] signal, final double[] kernel, final int kernelCenter, final double[] result,
716             final Convolver1DListener listener) {
717         final var kernelLength = kernel.length;
718         final var resultLength = result.length;
719         double accum;
720         int signalPos;
721 
722         if (listener != null) {
723             listener.onStartConvolution();
724         }
725 
726         for (var i = 0; i < resultLength; i++) {
727 
728             accum = 0.0;
729             signalPos = i - kernelCenter;
730             for (var j = 0; j < kernelLength; j++) {
731                 final var signalValue = getSignalValueRepeat(signal, signalPos - j);
732                 accum += signalValue * kernel[j];
733             }
734 
735             result[i] = accum;
736 
737             if (listener != null) {
738                 final var progress = (float) i / (float) resultLength;
739                 listener.onConvolveProgressChange(progress);
740             }
741         }
742 
743         if (listener != null) {
744             listener.onFinishConvolution();
745         }
746     }
747 
748     /**
749      * Internal method to determine signal value even when a position outside
750      * its boundaries is requested for repeat edge extension.
751      *
752      * @param signal signal to request a value for.
753      * @param pos    requested position.
754      * @return signal value assuming that signal is repeated indefinitely
755      * outside its boundaries.
756      */
757     protected static double getSignalValueRepeat(final double[] signal, int pos) {
758         final var signalLength = signal.length;
759         pos = pos % signalLength;
760         if (pos < 0) {
761             pos += signalLength;
762         }
763         return signal[pos];
764     }
765 
766     /**
767      * Internal method to convolve signal using a mirror extension method.
768      *
769      * @param signal       signal to be convolved.
770      * @param kernel       kernel to convolve with.
771      * @param kernelCenter kernel center.
772      * @param result       array where result is stored.
773      * @param listener     listener attending events generated during convolution.
774      */
775     private static void internalConvolveMirror(
776             final double[] signal, final double[] kernel, final int kernelCenter, final double[] result,
777             final Convolver1DListener listener) {
778         final var kernelLength = kernel.length;
779         final var resultLength = result.length;
780         double accum;
781         int signalPos;
782 
783         if (listener != null) {
784             listener.onStartConvolution();
785         }
786 
787         for (var i = 0; i < resultLength; i++) {
788 
789             accum = 0.0;
790             signalPos = i - kernelCenter;
791             for (var j = 0; j < kernelLength; j++) {
792                 final var signalValue = getSignalValueMirror(signal, signalPos - j);
793                 accum += signalValue * kernel[j];
794             }
795 
796             result[i] = accum;
797 
798             if (listener != null) {
799                 final var progress = (float) i / (float) resultLength;
800                 listener.onConvolveProgressChange(progress);
801             }
802         }
803 
804         if (listener != null) {
805             listener.onFinishConvolution();
806         }
807     }
808 
809     /**
810      * Internal method to determine signal value even when a position outside
811      * its boundaries is requested for mirror edge extension.
812      *
813      * @param signal signal to request a value for.
814      * @param pos    requested position.
815      * @return signal value when assuming that signal is mirrored outside its
816      * boundaries.
817      */
818     protected static double getSignalValueMirror(double[] signal, int pos) {
819         final var signalLength = signal.length;
820         var times = pos / signalLength;
821 
822         if (pos < 0) {
823             times = Math.abs(times);
824             if (times == 0) {
825                 times = 1;
826             }
827             pos = 2 * signalLength * times + pos;
828         }
829 
830         times = pos / signalLength;
831         final var reversed = (times % 2 != 0);
832 
833         pos = pos % signalLength;
834         if (pos < 0) {
835             pos += signalLength;
836         } else {
837             if (reversed) {
838                 // reversed
839                 pos = signalLength - 1 - pos;
840             }
841         }
842 
843         return signal[pos];
844     }
845 
846     /**
847      * Interface defining events produced by this class.
848      */
849     public interface Convolver1DListener {
850         /**
851          * Called when convolution starts.
852          */
853         void onStartConvolution();
854 
855         /**
856          * Called when convolution finishes.
857          */
858         void onFinishConvolution();
859 
860         /**
861          * Called when convolution progress changes.
862          *
863          * @param progress convolution progress as a value between 0.0f and
864          *                 1.0f.
865          */
866         void onConvolveProgressChange(final float progress);
867     }
868 }