Completed
Push — master ( 75110a...499ef6 )
by Patrick
01:20
created

HarmonicCalculator::getBowedDistance()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
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)
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)
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
            * $this->getPhysicalStringLength($harmonic);
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() * $this->getPhysicalStringLength($harmonic);
111
    }
112
113
    /**
114
     * Find the physical length of the harmonic's string.
115
     *
116
     * @param Harmonic $harmonic
117
     *
118
     * @return float
119
     */
120
    private function getPhysicalStringLength(Harmonic $harmonic): float
121
    {
122
      $string = $harmonic->getString();
123
124
      return $string instanceof InstrumentStringInterface
125
          ? $string->getPhysicalLength()
126
          : 500.0;
127
    }
128
129
    /**
130
     * Find the artificial harmonics that produce the given sounding note.
131
     *
132
     * @param \ExtendedStrings\Strings\Note                     $soundingNote
133
     * @param \ExtendedStrings\Strings\VibratingStringInterface $string
134
     *
135
     * @return Harmonic[]
136
     */
137
    private function findArtificialHarmonics(Note $soundingNote, VibratingStringInterface $string): array
138
    {
139
        $harmonics = [];
140
        $soundingNoteFrequency = $soundingNote->getFrequency();
141
        $stringFrequency = $string->getFrequency();
142
        foreach (range(6, 2) as $number) {
143
            $fundamental = $soundingNoteFrequency / $number;
144
            if ($fundamental > $stringFrequency) {
145
                $baseStop = Stop::fromFrequency($fundamental, $string);
146
                $ratio = ($number - 1) / $number;
147
                $halfStop = new Stop($ratio * $baseStop->getStringLength());
148
149
                $harmonics[] = new Harmonic($halfStop, $baseStop, $string);
150
            }
151
        }
152
153
        return $harmonics;
154
    }
155
156
    /**
157
     * Find the natural harmonics that produce the given sounding note.
158
     *
159
     * @param \ExtendedStrings\Strings\Note            $soundingNote
160
     * @param \ExtendedStrings\Strings\InstrumentStringInterface $string
161
     *
162
     * @return Harmonic[]
163
     */
164
    private function findNaturalHarmonics(Note $soundingNote, InstrumentStringInterface $string): array
165
    {
166
        $harmonics = [];
167
        $soundingCents = $soundingNote->getCents();
168
        foreach (range(1, 8) as $number) {
169
            // Convert harmonic number to the sounding frequency.
170
            $candidateFrequency = (new Stop(1 / $number))->getFrequency($string);
171
172
            // Calculate the difference in cents between the natural harmonic
173
            // frequency and the desired sounding note.
174
            $difference = abs(Cent::frequencyToCents($candidateFrequency) - $soundingCents);
175
176
            if ($difference <= $this->maxSoundingNoteDifference) {
177
                $stringLengths = Harmonic::getStringLengthsFromNumber($number, true);
178
                foreach ($stringLengths as $stringLength) {
179
                    $harmonics[] = new Harmonic(new Stop($stringLength), null, $string);
180
                }
181
            }
182
        }
183
184
        return $harmonics;
185
    }
186
}
187