Completed
Push — master ( 2095ba...9a6e4e )
by Patrick
02:15
created

HarmonicCalculator   A

Complexity

Total Complexity 15

Size/Duplication

Total Lines 144
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Importance

Changes 0
Metric Value
wmc 15
lcom 1
cbo 7
dl 0
loc 144
rs 10
c 0
b 0
f 0

6 Methods

Rating   Name   Duplication   Size   Complexity  
A findHarmonics() 0 17 2
A setDistanceConstraints() 0 5 1
A validateDistance() 0 9 3
A getPhysicalDistance() 0 11 2
A findArtificialHarmonics() 0 18 3
B findNaturalHarmonics() 0 22 4
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
        if ($harmonic->isNatural()) {
66
            return true;
67
        }
68
        $distance = $this->getPhysicalDistance($harmonic);
69
70
        return $distance >= $this->minDistance && $distance <= $this->maxDistance;
71
    }
72
73
    /**
74
     * Calculate the physical distance between two nodes of a harmonic.
75
     *
76
     * @param \ExtendedStrings\Strings\Harmonic $harmonic
77
     *
78
     * @return float
79
     */
80
    private function getPhysicalDistance(Harmonic $harmonic): float
81
    {
82
        $string = $harmonic->getString();
83
        $physicalLength = ($string instanceof InstrumentStringInterface)
84
            ? $string->getPhysicalLength()
85
            : 500.0;
86
        $basePhysical = $harmonic->getBaseStop()->getStringLength() * $physicalLength;
87
        $halfStopPhysical = $harmonic->getHalfStop()->getStringLength() * $physicalLength;
88
89
        return $basePhysical - $halfStopPhysical;
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