You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

179 lines
5.3KB

  1. // Copyright 2012 Olivier Gillet.
  2. //
  3. // Author: Olivier Gillet (ol.gillet@gmail.com)
  4. //
  5. // This program is free software: you can redistribute it and/or modify
  6. // it under the terms of the GNU General Public License as published by
  7. // the Free Software Foundation, either version 3 of the License, or
  8. // (at your option) any later version.
  9. // This program is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU General Public License for more details.
  13. // You should have received a copy of the GNU General Public License
  14. // along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. //
  16. // -----------------------------------------------------------------------------
  17. //
  18. // Stack of currently pressed keys.
  19. //
  20. // Currently pressed keys are stored as a linked list. The linked list is used
  21. // as a LIFO stack to allow monosynth-like behaviour. An example of such
  22. // behaviour is:
  23. // player presses and holds C4-> C4 is played.
  24. // player presses and holds C5 (while holding C4) -> C5 is played.
  25. // player presses and holds G4 (while holding C4&C5)-> G4 is played.
  26. // player releases C5 -> G4 is played.
  27. // player releases G4 -> C4 is played.
  28. //
  29. // The nodes used in the linked list are pre-allocated from a pool of 16
  30. // nodes, so the "pointers" (to the root element for example) are not actual
  31. // pointers, but indices of an element in the pool.
  32. //
  33. // Additionally, an array of pointers is stored to allow random access to the
  34. // n-th note, sorted by ascending order of pitch (for arpeggiation).
  35. #ifndef EDGES_NOTE_STACK_H_
  36. #define EDGES_NOTE_STACK_H_
  37. #include "avrlibx/avrlibx.h"
  38. #include <string.h>
  39. namespace edges {
  40. static const uint8_t kFreeSlot = 0xff;
  41. struct NoteEntry {
  42. uint8_t note;
  43. uint8_t velocity;
  44. uint8_t next_ptr; // Base 1.
  45. };
  46. // This looks crazy, but we are more concerned about RAM used than code size here.
  47. template<uint8_t capacity>
  48. class NoteStack {
  49. public:
  50. NoteStack() { }
  51. void Init() { Clear(); }
  52. void NoteOn(uint8_t note, uint8_t velocity) {
  53. // Remove the note from the list first (in case it is already here).
  54. NoteOff(note);
  55. // In case of saturation, remove the least recently played note from the
  56. // stack.
  57. if (size_ == capacity) {
  58. uint8_t least_recent_note;
  59. for (uint8_t i = 1; i <= capacity; ++i) {
  60. if (pool_[i].next_ptr == 0) {
  61. least_recent_note = pool_[i].note;
  62. }
  63. }
  64. NoteOff(least_recent_note);
  65. }
  66. // Now we are ready to insert the new note. Find a free slot to insert it.
  67. uint8_t free_slot;
  68. for (uint8_t i = 1; i <= capacity; ++i) {
  69. if (pool_[i].note == kFreeSlot) {
  70. free_slot = i;
  71. break;
  72. }
  73. }
  74. pool_[free_slot].next_ptr = root_ptr_;
  75. pool_[free_slot].note = note;
  76. pool_[free_slot].velocity = velocity;
  77. root_ptr_ = free_slot;
  78. // The last step consists in inserting the note in the sorted list.
  79. for (uint8_t i = 0; i < size_; ++i) {
  80. if (pool_[sorted_ptr_[i]].note > note) {
  81. for (uint8_t j = size_; j > i; --j) {
  82. sorted_ptr_[j] = sorted_ptr_[j - 1];
  83. }
  84. sorted_ptr_[i] = free_slot;
  85. free_slot = 0;
  86. break;
  87. }
  88. }
  89. if (free_slot) {
  90. sorted_ptr_[size_] = free_slot;
  91. }
  92. ++size_;
  93. }
  94. void NoteOff(uint8_t note) {
  95. uint8_t current = root_ptr_;
  96. uint8_t previous = 0;
  97. while (current) {
  98. if (pool_[current].note == note) {
  99. break;
  100. }
  101. previous = current;
  102. current = pool_[current].next_ptr;
  103. }
  104. if (current) {
  105. if (previous) {
  106. pool_[previous].next_ptr = pool_[current].next_ptr;
  107. } else {
  108. root_ptr_ = pool_[current].next_ptr;
  109. }
  110. for (uint8_t i = 0; i < size_; ++i) {
  111. if (sorted_ptr_[i] == current) {
  112. for (uint8_t j = i; j < size_ - 1; ++j) {
  113. sorted_ptr_[j] = sorted_ptr_[j + 1];
  114. }
  115. break;
  116. }
  117. }
  118. pool_[current].next_ptr = 0;
  119. pool_[current].note = kFreeSlot;
  120. pool_[current].velocity = 0;
  121. --size_;
  122. }
  123. }
  124. void Clear() {
  125. size_ = 0;
  126. memset(pool_ + 1, 0, sizeof(NoteEntry) * capacity);
  127. memset(sorted_ptr_ + 1, 0, capacity);
  128. root_ptr_ = 0;
  129. for (uint8_t i = 0; i <= capacity; ++i) {
  130. pool_[i].note = kFreeSlot;
  131. }
  132. }
  133. uint8_t size() const { return size_; }
  134. const NoteEntry& most_recent_note() const { return pool_[root_ptr_]; }
  135. const NoteEntry& least_recent_note() const {
  136. uint8_t current = root_ptr_;
  137. while (current && pool_[current].next_ptr) {
  138. current = pool_[current].next_ptr;
  139. }
  140. return pool_[current];
  141. }
  142. const NoteEntry& played_note(uint8_t index) const {
  143. uint8_t current = root_ptr_;
  144. index = size_ - index - 1;
  145. for (uint8_t i = 0; i < index; ++i) {
  146. current = pool_[current].next_ptr;
  147. }
  148. return pool_[current];
  149. }
  150. const NoteEntry& sorted_note(uint8_t index) const {
  151. return pool_[sorted_ptr_[index]];
  152. }
  153. const NoteEntry& note(uint8_t index) const { return pool_[index]; }
  154. const NoteEntry& dummy() const { return pool_[0]; }
  155. private:
  156. uint8_t size_;
  157. NoteEntry pool_[capacity + 1]; // First element is a dummy node!
  158. uint8_t root_ptr_; // Base 1.
  159. uint8_t sorted_ptr_[capacity + 1]; // Base 1.
  160. DISALLOW_COPY_AND_ASSIGN(NoteStack);
  161. };
  162. } // namespace edges
  163. #endif // EDGES_NOTE_STACK_H_