Completed
Push — master ( 7b220e...d0f73c )
by Patrick
08:53
created

HarmonicCalculator::setDistanceConstraints()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 5
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 2
1
<?php
2
3
declare(strict_types = 1);
4
5
namespace ExtendedStrings\Strings;
6
7
class HarmonicCalculator
8
{
9
    private $minDistance = 1.0;
10
    private $maxDistance = 120.0;
11
12
    /**
13
     * Returns a list of possible harmonics that produce a given sounding note.
14
     *
15
     * @param Note                $soundingNote The desired sounding note of
16
     *                                          the harmonic.
17
     * @param InstrumentInterface $instrument   The instrument.
18
     * @param float               $tolerance    The maximum deviation (cents)
19
     *                                          between the desired sounding
20
     *                                          note and a natural harmonic.
21
     *
22
     * @return Harmonic[]
23
     */
24
    public function findHarmonics(Note $soundingNote, InstrumentInterface $instrument, float $tolerance = 50.0): array
25
    {
26
        $harmonics = [];
27
        foreach ($instrument->getStrings() as $string) {
28
            $harmonics = array_merge(
29
                $harmonics,
30
                $this->findNaturalHarmonics($soundingNote, $string, $tolerance),
31
                $this->findArtificialHarmonics($soundingNote, $string)
32
            );
33
        }
34
35
        $harmonics = array_filter($harmonics, function (Harmonic $harmonic) {
36
            return $this->validateDistance($harmonic);
37
        });
38
39
        return $harmonics;
40
    }
41
42
    /**
43
     * Set the minimum and maximum distance between harmonic stops (in mm).
44
     *
45
     * @param float $minDistance
46
     * @param float $maxDistance
47
     */
48
    public function setDistanceConstraints(float $minDistance, float $maxDistance)
49
    {
50
        $this->minDistance = $minDistance;
51
        $this->maxDistance = $maxDistance;
52
    }
53
54
    /**
55
     * Check that the harmonic is within the configured distance constraints.
56
     *
57
     * @see HarmonicCalculator::setDistanceConstraints()
58
     *
59
     * @param \ExtendedStrings\Strings\Harmonic $harmonic
60
     *
61
     * @return bool
62
     */
63
    private function validateDistance(Harmonic $harmonic): bool
64
    {
65
        $string = $harmonic->getString();
66
        $physicalStringLength = ($string instanceof InstrumentStringInterface)
67
            ? $string->getLength()
68
            : 500.0;
69
        if ($harmonic->isNatural()) {
70
            return $harmonic->getBaseStop()->getStringLength() * $physicalStringLength >= $this->minDistance;
71
        }
72
        $distance = ($harmonic->getBaseStop()->getStringLength()
73
                - $harmonic->getHalfStop()->getStringLength()
74
            ) * $physicalStringLength;
75
76
        return $distance >= $this->minDistance && $distance <= $this->maxDistance;
77
    }
78
79
    /**
80
     * Find the artificial harmonics that produce the given sounding note.
81
     *
82
     * @param \ExtendedStrings\Strings\Note                     $soundingNote
83
     * @param \ExtendedStrings\Strings\VibratingStringInterface $string
84
     *
85
     * @return Harmonic[]
86
     */
87
    private function findArtificialHarmonics(Note $soundingNote, VibratingStringInterface $string): array
88
    {
89
        $harmonics = [];
90
        $soundingNoteFrequency = $soundingNote->getFrequency();
91
        $stringFrequency = $string->getFrequency();
92
        foreach (range(6, 2) as $number) {
93
            $fundamental = $soundingNoteFrequency / $number;
94
            if ($fundamental > $stringFrequency) {
95
                $baseStop = Stop::fromFrequency($fundamental, $string);
96
                $ratio = ($number - 1) / $number;
97
                $halfStop = new Stop($ratio * $baseStop->getStringLength());
98
99
                $harmonics[] = new Harmonic($halfStop, $baseStop, $string);
100
            }
101
        }
102
103
        return $harmonics;
104
    }
105
106
    /**
107
     * Find the natural harmonics that produce the given sounding note.
108
     *
109
     * @param \ExtendedStrings\Strings\Note            $soundingNote
110
     * @param \ExtendedStrings\Strings\InstrumentStringInterface $string
111
     * @param float                                    $tolerance
112
     *
113
     * @return Harmonic[]
114
     */
115
    private function findNaturalHarmonics(Note $soundingNote, InstrumentStringInterface $string, float $tolerance = 50.0): array
116
    {
117
        $harmonics = [];
118
        $soundingCents = $soundingNote->getCents();
119
        foreach (range(1, 8) as $number) {
120
            // Convert harmonic number to the sounding frequency.
121
            $candidateFrequency = (new Stop(1 / $number))->getFrequency($string);
122
123
            // Calculate the difference in cents between the natural harmonic
124
            // frequency and the desired sounding note.
125
            $difference = abs(Cent::frequencyToCents($candidateFrequency) - $soundingCents);
126
127
            if ($difference <= $tolerance) {
128
                $stringLengths = Harmonic::getStringLengthsFromNumber($number, true);
129
                foreach ($stringLengths as $stringLength) {
130
                    $harmonics[] = new Harmonic(new Stop($stringLength), null, $string);
131
                }
132
            }
133
        }
134
135
        return $harmonics;
136
    }
137
}
138