1 /*
2   ==============================================================================
3 
4    This file is part of the JUCE library.
5    Copyright (c) 2020 - Raw Material Software Limited
6 
7    JUCE is an open source library subject to commercial or open-source
8    licensing.
9 
10    The code included in this file is provided under the terms of the ISC license
11    http://www.isc.org/downloads/software-support-policy/isc-license. Permission
12    To use, copy, modify, and/or distribute this software for any purpose with or
13    without fee is hereby granted provided that the above copyright notice and
14    this permission notice appear in all copies.
15 
16    JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
17    EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
18    DISCLAIMED.
19 
20   ==============================================================================
21 */
22 
23 namespace juce
24 {
25 
MidiKeyboardState()26 MidiKeyboardState::MidiKeyboardState()
27 {
28     zerostruct (noteStates);
29 }
30 
~MidiKeyboardState()31 MidiKeyboardState::~MidiKeyboardState()
32 {
33 }
34 
35 //==============================================================================
reset()36 void MidiKeyboardState::reset()
37 {
38     const ScopedLock sl (lock);
39     zerostruct (noteStates);
40     eventsToAdd.clear();
41 }
42 
isNoteOn(const int midiChannel,const int n) const43 bool MidiKeyboardState::isNoteOn (const int midiChannel, const int n) const noexcept
44 {
45     jassert (midiChannel > 0 && midiChannel <= 16);
46 
47     return isPositiveAndBelow (n, 128)
48             && (noteStates[n] & (1 << (midiChannel - 1))) != 0;
49 }
50 
isNoteOnForChannels(const int midiChannelMask,const int n) const51 bool MidiKeyboardState::isNoteOnForChannels (const int midiChannelMask, const int n) const noexcept
52 {
53     return isPositiveAndBelow (n, 128)
54             && (noteStates[n] & midiChannelMask) != 0;
55 }
56 
noteOn(const int midiChannel,const int midiNoteNumber,const float velocity)57 void MidiKeyboardState::noteOn (const int midiChannel, const int midiNoteNumber, const float velocity)
58 {
59     jassert (midiChannel > 0 && midiChannel <= 16);
60     jassert (isPositiveAndBelow (midiNoteNumber, 128));
61 
62     const ScopedLock sl (lock);
63 
64     if (isPositiveAndBelow (midiNoteNumber, 128))
65     {
66         const int timeNow = (int) Time::getMillisecondCounter();
67         eventsToAdd.addEvent (MidiMessage::noteOn (midiChannel, midiNoteNumber, velocity), timeNow);
68         eventsToAdd.clear (0, timeNow - 500);
69 
70         noteOnInternal (midiChannel, midiNoteNumber, velocity);
71     }
72 }
73 
noteOnInternal(const int midiChannel,const int midiNoteNumber,const float velocity)74 void MidiKeyboardState::noteOnInternal  (const int midiChannel, const int midiNoteNumber, const float velocity)
75 {
76     if (isPositiveAndBelow (midiNoteNumber, 128))
77     {
78         noteStates[midiNoteNumber] = static_cast<uint16> (noteStates[midiNoteNumber] | (1 << (midiChannel - 1)));
79         listeners.call ([&] (Listener& l) { l.handleNoteOn (this, midiChannel, midiNoteNumber, velocity); });
80     }
81 }
82 
noteOff(const int midiChannel,const int midiNoteNumber,const float velocity)83 void MidiKeyboardState::noteOff (const int midiChannel, const int midiNoteNumber, const float velocity)
84 {
85     const ScopedLock sl (lock);
86 
87     if (isNoteOn (midiChannel, midiNoteNumber))
88     {
89         const int timeNow = (int) Time::getMillisecondCounter();
90         eventsToAdd.addEvent (MidiMessage::noteOff (midiChannel, midiNoteNumber), timeNow);
91         eventsToAdd.clear (0, timeNow - 500);
92 
93         noteOffInternal (midiChannel, midiNoteNumber, velocity);
94     }
95 }
96 
noteOffInternal(const int midiChannel,const int midiNoteNumber,const float velocity)97 void MidiKeyboardState::noteOffInternal  (const int midiChannel, const int midiNoteNumber, const float velocity)
98 {
99     if (isNoteOn (midiChannel, midiNoteNumber))
100     {
101         noteStates[midiNoteNumber] = static_cast<uint16> (noteStates[midiNoteNumber] & ~(1 << (midiChannel - 1)));
102         listeners.call ([&] (Listener& l) { l.handleNoteOff (this, midiChannel, midiNoteNumber, velocity); });
103     }
104 }
105 
allNotesOff(const int midiChannel)106 void MidiKeyboardState::allNotesOff (const int midiChannel)
107 {
108     const ScopedLock sl (lock);
109 
110     if (midiChannel <= 0)
111     {
112         for (int i = 1; i <= 16; ++i)
113             allNotesOff (i);
114     }
115     else
116     {
117         for (int i = 0; i < 128; ++i)
118             noteOff (midiChannel, i, 0.0f);
119     }
120 }
121 
processNextMidiEvent(const MidiMessage & message)122 void MidiKeyboardState::processNextMidiEvent (const MidiMessage& message)
123 {
124     if (message.isNoteOn())
125     {
126         noteOnInternal (message.getChannel(), message.getNoteNumber(), message.getFloatVelocity());
127     }
128     else if (message.isNoteOff())
129     {
130         noteOffInternal (message.getChannel(), message.getNoteNumber(), message.getFloatVelocity());
131     }
132     else if (message.isAllNotesOff())
133     {
134         for (int i = 0; i < 128; ++i)
135             noteOffInternal (message.getChannel(), i, 0.0f);
136     }
137 }
138 
processNextMidiBuffer(MidiBuffer & buffer,const int startSample,const int numSamples,const bool injectIndirectEvents)139 void MidiKeyboardState::processNextMidiBuffer (MidiBuffer& buffer,
140                                                const int startSample,
141                                                const int numSamples,
142                                                const bool injectIndirectEvents)
143 {
144     const ScopedLock sl (lock);
145 
146     for (const auto metadata : buffer)
147         processNextMidiEvent (metadata.getMessage());
148 
149     if (injectIndirectEvents)
150     {
151         const int firstEventToAdd = eventsToAdd.getFirstEventTime();
152         const double scaleFactor = numSamples / (double) (eventsToAdd.getLastEventTime() + 1 - firstEventToAdd);
153 
154         for (const auto metadata : eventsToAdd)
155         {
156             const auto pos = jlimit (0, numSamples - 1, roundToInt ((metadata.samplePosition - firstEventToAdd) * scaleFactor));
157             buffer.addEvent (metadata.getMessage(), startSample + pos);
158         }
159     }
160 
161     eventsToAdd.clear();
162 }
163 
164 //==============================================================================
addListener(Listener * listener)165 void MidiKeyboardState::addListener (Listener* listener)
166 {
167     const ScopedLock sl (lock);
168     listeners.add (listener);
169 }
170 
removeListener(Listener * listener)171 void MidiKeyboardState::removeListener (Listener* listener)
172 {
173     const ScopedLock sl (lock);
174     listeners.remove (listener);
175 }
176 
177 } // namespace juce
178