HarmonicCalculator::findArtificialHarmonics()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 18
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 18
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 12
nc 3
nop 2
1
<?php
2
3
declare(strict_types = 1);
4
5
namespace ExtendedStrings\Strings;
6
7
class HarmonicCalculator
8
{
9
    private $minStopDistance = 1.0;
10
    private $maxStopDistance = 120.0;
11
    private $minBowedDistance = 20.0;
12
    private $maxSoundingNoteDifference = 50.0;
13
14
    /**
15
     * Returns a list of possible harmonics that produce a given sounding note.
16
     *
17
     * @param Note                $soundingNote The desired sounding note of
18
     *                                          the harmonic.
19
     * @param InstrumentInterface $instrument   The instrument.
20
     *
21
     * @return Harmonic[]
22
     */
23
    public function findHarmonics(Note $soundingNote, InstrumentInterface $instrument): array
24
    {
25
        $harmonics = [];
26
        foreach ($instrument->getStrings() as $string) {
27
            $harmonics = array_merge(
28
                $harmonics,
29
                $this->findNaturalHarmonics($soundingNote, $string),
30
                $this->findArtificialHarmonics($soundingNote, $string)
31
            );
32
        }
33
34
        $harmonics = array_filter($harmonics, function (Harmonic $harmonic) {
35
            return $this->validatePhysicalDistance($harmonic);
36
        });
37
38
        return $harmonics;
39
    }
40
41
    /**
42
     * Set constraints on the physical distance between harmonic stops.
43
     *
44
     * @param float $minStopDistance  The minimum distance between stops (mm).
45
     * @param float $maxStopDistance  The maximum distance between stops (mm).
46
     * @param float $minBowedDistance The minimum distance between the upper
47
     *                                harmonic stop and the bridge (mm).
48
     */
49
    public function setPhysicalDistanceConstraints(float $minStopDistance, float $maxStopDistance, float $minBowedDistance): void
50
    {
51
        $this->minStopDistance = $minStopDistance;
52
        $this->maxStopDistance = $maxStopDistance;
53
        $this->minBowedDistance = $minBowedDistance;
54
    }
55
56
    /**
57
     * Set the max difference between the sounding note and natural harmonics.
58
     *
59
     * @param float $difference The difference in cents (default: 50.0).
60
     */
61
    public function setMaxSoundingNoteDifference(float $difference): void
62
    {
63
        $this->maxSoundingNoteDifference = $difference;
64
    }
65
66
    /**
67
     * Check that the harmonic is within the configured distance constraints.
68
     *
69
     * @see HarmonicCalculator::setPhysicalDistanceConstraints()
70
     *
71
     * @param \ExtendedStrings\Strings\Harmonic $harmonic
72
     *
73
     * @return bool
74
     */
75
    private function validatePhysicalDistance(Harmonic $harmonic): bool
76
    {
77
        if (!$harmonic->isNatural()) {
78
            $distance = $this->getPhysicalDistanceBetweenStops($harmonic);
79
80
            if ($distance < $this->minStopDistance || $distance > $this->maxStopDistance) {
81
                return false;
82
            }
83
        }
84
85
        return $this->getBowedDistance($harmonic) >= $this->minBowedDistance;
86
    }
87
88
    /**
89
     * Find the physical distance between the stops of a harmonic.
90
     *
91
     * @param \ExtendedStrings\Strings\Harmonic $harmonic
92
     *
93
     * @return float
94
     */
95
    private function getPhysicalDistanceBetweenStops(Harmonic $harmonic): float
96
    {
97
        return ($harmonic->getBaseStop()->getStringLength() - $harmonic->getHalfStop()->getStringLength())
98
            * $harmonic->getString()->getPhysicalLength();
99
    }
100
101
    /**
102
     * Find the physical distance between a harmonic's half-stop and the bridge.
103
     *
104
     * @param \ExtendedStrings\Strings\Harmonic $harmonic
105
     *
106
     * @return float
107
     */
108
    private function getBowedDistance(Harmonic $harmonic): float
109
    {
110
        return $harmonic->getHalfStop()->getStringLength() * $harmonic->getString()->getPhysicalLength();
111
    }
112
113
    /**
114
     * Find the artificial harmonics that produce the given sounding note.
115
     *
116
     * @param \ExtendedStrings\Strings\Note                     $soundingNote
117
     * @param \ExtendedStrings\Strings\VibratingStringInterface $string
118
     *
119
     * @return Harmonic[]
120
     */
121
    private function findArtificialHarmonics(Note $soundingNote, VibratingStringInterface $string): array
122
    {
123
        $harmonics = [];
124
        $soundingNoteFrequency = $soundingNote->getFrequency();
125
        $stringFrequency = $string->getFrequency();
126
        foreach (range(6, 2) as $number) {
127
            $fundamental = $soundingNoteFrequency / $number;
128
            if (Math::isGreaterThan($fundamental, $stringFrequency)) {
129
                $baseStop = Stop::fromFrequency($fundamental, $string);
130
                $ratio = ($number - 1) / $number;
131
                $halfStop = new Stop($ratio * $baseStop->getStringLength());
132
133
                $harmonics[] = new Harmonic($halfStop, $baseStop, $string);
134
            }
135
        }
136
137
        return $harmonics;
138
    }
139
140
    /**
141
     * Find the natural harmonics that produce the given sounding note.
142
     *
143
     * @param \ExtendedStrings\Strings\Note                     $soundingNote
144
     * @param \ExtendedStrings\Strings\VibratingStringInterface $string
145
     *
146
     * @return Harmonic[]
147
     */
148
    private function findNaturalHarmonics(Note $soundingNote, VibratingStringInterface $string): array
149
    {
150
        $harmonics = [];
151
        $soundingCents = $soundingNote->getCents();
152
        foreach (range(1, 8) as $number) {
153
            // Convert harmonic number to the sounding frequency.
154
            $candidateFrequency = (new Stop(1 / $number))->getFrequency($string);
155
156
            // Calculate the difference in cents between the natural harmonic
157
            // frequency and the desired sounding note.
158
            $difference = abs(Cent::frequencyToCents($candidateFrequency) - $soundingCents);
159
160
            if ($difference <= $this->maxSoundingNoteDifference) {
161
                $stringLengths = Harmonic::getStringLengthsFromNumber($number, true);
162
                foreach ($stringLengths as $stringLength) {
163
                    $harmonics[] = new Harmonic(new Stop($stringLength), null, $string);
164
                }
165
            }
166
        }
167
168
        return $harmonics;
169
    }
170
}
171