Completed
Push — master ( 8c0b34...7b920c )
by Patrick
01:31
created

HarmonicCalculator::getPhysicalDistance()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 11
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 8
nc 2
nop 1

1 Method

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