├── ..gitignore.un~ ├── .gitignore ├── README.md ├── Source ├── CustomLookAndFeel.h ├── Main.cpp ├── MainComponent.cpp ├── MainComponent.h ├── NoteControls.cpp ├── NoteControls.h ├── StepControls.cpp ├── StepControls.h └── TogglePad.h ├── aseSequencer Project.jucer └── doc ├── ASE Diagrams - UI Interface.png ├── ASE Diagrams -Architecture Template.png ├── ASE Diagrams -Class Diagram.png ├── ASE Diagrams -Sequence Diagram 1.png ├── ASE Diagrams -Sequence Diagram 2.png ├── ASE Diagrams -Sequence Diagram 3.png ├── ASE Diagrams -State Machine Diagram.png ├── ASE Diagrams -Use Case Diagram 1.png ├── ASE Diagrams -Use Case Diagram 2.png ├── ASE_Gregorini_Silvio_Journal.pdf └── screenshot-sequencer.png /..gitignore.un~: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-gregorini003/juce-midi-step-sequencer/9c159deaad000c006780fef3d53267d72d9c1d3d/..gitignore.un~ -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Builds/ 2 | JuceLibraryCode/ 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MIDI Step Sequencer in JUCE 2 | This repository contains the source code and the documentation of a multitrack, MIDI step sequencer written in JUCE. This app allows the user to create melodic patterns and it will output a set of MIDI commands to trigger software or hardware synthesizers. The pattern can be played back in real-time or stored in the computer. The software was created using JUCE v5.4.3 and exported in Visual Studio 2017. 3 | 4 | [Documentation](https://github.com/s-gregorini003/ase-sequencer-project/blob/master/doc/ASE_Gregorini_Silvio_Journal.pdf) 5 | 6 | ![Screenshot of the finished app](https://github.com/s-gregorini003/ase-sequencer-project/blob/master/doc/screenshot-sequencer.png) 7 | -------------------------------------------------------------------------------- /Source/CustomLookAndFeel.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | CustomLookAndFeel.h 5 | Created: 3 May 2019 8:30:02pm 6 | Author: Silvio 7 | 8 | ============================================================================== 9 | */ 10 | 11 | #pragma once 12 | 13 | #include "../JuceLibraryCode/JuceHeader.h" 14 | 15 | //============================================================================== 16 | /* 17 | */ 18 | class CustomLookAndFeel : public LookAndFeel_V4 19 | 20 | 21 | { 22 | public: 23 | 24 | void drawRotarySlider(Graphics &g, int x, int y, int width, int height, float sliderPosProportional, float rotaryStartAngle, float rotaryEndAngle, Slider &) override 25 | { 26 | float diameter = jmin(width, height); 27 | float radius = diameter / 2; 28 | float centreX = x + width / 2; 29 | float centreY = y + height / 2; 30 | float rx = centreX - radius; 31 | float ry = centreY - radius; 32 | float angle = rotaryStartAngle + (sliderPosProportional * (rotaryEndAngle - rotaryStartAngle)); 33 | 34 | juce::Rectangle dialArea(rx, ry, diameter, diameter); 35 | 36 | Path dialTick, backgndTick; 37 | 38 | dialTick.addPieSegment(dialArea, rotaryStartAngle, angle, 0.6); 39 | backgndTick.addPieSegment(dialArea, rotaryStartAngle, rotaryEndAngle, 0.6); 40 | 41 | g.setColour(Colour(20, 31, 51)); 42 | g.fillPath(backgndTick); 43 | 44 | g.setColour(Colour(223, 116, 12)); 45 | g.fillPath(dialTick); 46 | //PathStrokeType::JointStyle::beveled 47 | 48 | } 49 | 50 | void drawLinearSlider(Graphics &g, int x, int y, int width, int height, float sliderPos, float minSliderPos, float maxSliderPos, const Slider::SliderStyle, Slider &) override 51 | { 52 | 53 | juce::Rectangle sliderArea(x, y, width, height); 54 | 55 | 56 | g.setColour(Colour(20, 31, 51)); 57 | g.fillRect(x, y, width, height); 58 | 59 | g.setColour(Colour(223, 116, 12)); 60 | g.fillRect((float)x, sliderPos, (float)width, y + (height - sliderPos)); 61 | 62 | } 63 | 64 | void drawLabel(Graphics &g, Label &label) override 65 | { 66 | 67 | if (!label.isBeingEdited()) 68 | { 69 | auto alpha = label.isEnabled() ? 1.0f : 0.5f; 70 | const Font font(12, 1); 71 | 72 | g.setColour(Colour(223, 116, 12)); 73 | g.setFont(font); 74 | 75 | auto textArea = getLabelBorderSize(label).subtractedFrom(label.getLocalBounds()); 76 | 77 | g.drawFittedText(label.getText(), textArea, label.getJustificationType(), 78 | jmax(1, (int)(textArea.getHeight() / font.getHeight())), 79 | label.getMinimumHorizontalScale()); 80 | 81 | } 82 | 83 | } 84 | 85 | void drawToggleButton(Graphics& g, ToggleButton& button, 86 | bool shouldDrawButtonAsHighlighted, bool shouldDrawButtonAsDown) override 87 | { 88 | auto fontSize = jmin(15.0f, button.getHeight() * 0.75f); 89 | auto tickWidth = fontSize * 1.1f; 90 | 91 | Colour fillColour = (button.getToggleState() == true ? Colour(223, 116, 12) : Colour(20, 31, 51)); 92 | g.fillAll(fillColour); 93 | 94 | /*drawTickBox(g, button, 4.0f, (button.getHeight() - tickWidth) * 0.5f, 95 | tickWidth, tickWidth, 96 | button.getToggleState(), 97 | button.isEnabled(), 98 | shouldDrawButtonAsHighlighted, 99 | shouldDrawButtonAsDown);*/ 100 | 101 | g.setColour(button.findColour(ToggleButton::textColourId)); 102 | g.setFont(fontSize); 103 | 104 | if (!button.isEnabled()) 105 | g.setOpacity(0.5f); 106 | 107 | g.drawFittedText(button.getButtonText(), 108 | button.getLocalBounds().withTrimmedLeft(roundToInt(tickWidth) + 10) 109 | .withTrimmedRight(2), 110 | Justification::centredLeft, 10); 111 | } 112 | 113 | }; 114 | 115 | 116 | class CustomLookAndFeel2 : public LookAndFeel_V4 117 | 118 | { 119 | public: 120 | 121 | void drawLabel(Graphics &g, Label &label) override 122 | { 123 | 124 | if (!label.isBeingEdited()) 125 | { 126 | const Font font(14, 0); 127 | 128 | g.setColour(Colour(250, 250, 250)); 129 | g.setFont(font); 130 | 131 | BorderSize borderSize(0, 0, 0, 0); 132 | label.setBorderSize(borderSize); 133 | 134 | juce::Rectangle rect(label.getX(), label.getY(), label.getWidth(), label.getHeight()); 135 | 136 | auto textArea = getLabelBorderSize(label).subtractedFrom(rect); 137 | 138 | g.drawFittedText(label.getText(), textArea, Justification::horizontallyCentred, 139 | jmax(1, (int)(textArea.getHeight() / font.getHeight())), 140 | label.getMinimumHorizontalScale()); 141 | 142 | 143 | } 144 | 145 | } 146 | 147 | 148 | 149 | void drawComboBox(Graphics& g, int width, int height, const bool isMouseButtonDown, int buttonX, int buttonY, int buttonW, int buttonH, ComboBox& box) override 150 | { 151 | 152 | juce::Rectangle boxBounds(0, 0, width, height); 153 | 154 | 155 | g.setColour(Colour(20, 31, 51)); 156 | g.fillRect(boxBounds.toFloat()); 157 | 158 | 159 | } 160 | 161 | void drawPopupMenuBackground(Graphics& g, int width, int height) override 162 | { 163 | auto background = Colour(20, 31, 51); 164 | 165 | g.fillAll(background); 166 | } 167 | 168 | Font getComboBoxFont(ComboBox& box) override 169 | { 170 | Font font(12, 0); 171 | 172 | return font; 173 | } 174 | 175 | void positionComboBoxText(ComboBox& box, Label& label) override 176 | { 177 | label.setBounds(0, 0, 178 | box.getWidth(), 179 | box.getHeight() - 2); 180 | 181 | label.setFont(getComboBoxFont(box)); 182 | 183 | label.setJustificationType(Justification::centred); 184 | } 185 | 186 | void drawPopupMenuItem(Graphics& g, const juce::Rectangle& area, 187 | const bool isSeparator, const bool isActive, 188 | const bool isHighlighted, const bool isTicked, 189 | const bool hasSubMenu, const String& text, 190 | const String& shortcutKeyText, 191 | const Drawable* icon, const Colour* const textColourToUse) override 192 | { 193 | if (isSeparator) 194 | { 195 | auto r = area.reduced(5, 0); 196 | r.removeFromTop(roundToInt((r.getHeight() * 0.5f) - 0.5f)); 197 | 198 | g.setColour(findColour(PopupMenu::textColourId).withAlpha(0.3f)); 199 | g.fillRect(r.removeFromTop(1)); 200 | } 201 | else 202 | { 203 | auto textColour = (textColourToUse == nullptr ? findColour(PopupMenu::textColourId) 204 | : *textColourToUse); 205 | 206 | auto r = area.reduced(1); 207 | 208 | if (isHighlighted && isActive) 209 | { 210 | g.setColour(Colour(8, 12, 20)); 211 | g.fillRect(r); 212 | 213 | g.setColour(findColour(PopupMenu::highlightedTextColourId)); 214 | } 215 | else 216 | { 217 | g.setColour(textColour.withMultipliedAlpha(isActive ? 1.0f : 0.5f)); 218 | } 219 | 220 | r.reduce(jmin(5, area.getWidth() / 20), 0); 221 | 222 | auto font = getPopupMenuFont(); 223 | 224 | auto maxFontHeight = r.getHeight() / 1.3f; 225 | 226 | if (font.getHeight() > maxFontHeight) 227 | font.setHeight(maxFontHeight); 228 | 229 | g.setFont(font); 230 | 231 | auto iconArea = r.removeFromLeft(roundToInt(maxFontHeight)).toFloat(); 232 | 233 | if (icon != nullptr) 234 | { 235 | icon->drawWithin(g, iconArea, RectanglePlacement::centred | RectanglePlacement::onlyReduceInSize, 1.0f); 236 | r.removeFromLeft(roundToInt(maxFontHeight * 0.5f)); 237 | } 238 | else if (isTicked) 239 | { 240 | auto tick = getTickShape(1.0f); 241 | g.fillPath(tick, tick.getTransformToScaleToFit(iconArea.reduced(iconArea.getWidth() / 5, 0).toFloat(), true)); 242 | } 243 | 244 | if (hasSubMenu) 245 | { 246 | auto arrowH = 0.6f * getPopupMenuFont().getAscent(); 247 | 248 | auto x = static_cast (r.removeFromRight((int)arrowH).getX()); 249 | auto halfH = static_cast (r.getCentreY()); 250 | 251 | Path path; 252 | path.startNewSubPath(x, halfH - arrowH * 0.5f); 253 | path.lineTo(x + arrowH * 0.6f, halfH); 254 | path.lineTo(x, halfH + arrowH * 0.5f); 255 | 256 | g.strokePath(path, PathStrokeType(2.0f)); 257 | } 258 | 259 | r.removeFromRight(3); 260 | g.drawFittedText(text, r, Justification::centredLeft, 1); 261 | 262 | if (shortcutKeyText.isNotEmpty()) 263 | { 264 | auto f2 = font; 265 | f2.setHeight(f2.getHeight() * 0.75f); 266 | f2.setHorizontalScale(0.95f); 267 | g.setFont(f2); 268 | 269 | g.drawText(shortcutKeyText, r, Justification::centredRight, true); 270 | } 271 | } 272 | } 273 | 274 | Label* createSliderTextBox(Slider& slider) override 275 | { 276 | auto* l = LookAndFeel_V2::createSliderTextBox(slider); 277 | 278 | if (getCurrentColourScheme() == LookAndFeel_V4::getGreyColourScheme() && (slider.getSliderStyle() == Slider::LinearBar 279 | || slider.getSliderStyle() == Slider::LinearBarVertical)) 280 | { 281 | l->setColour(Label::textColourId, Colours::black.withAlpha(0.7f)); 282 | } 283 | 284 | return l; 285 | } 286 | }; -------------------------------------------------------------------------------- /Source/Main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | This file was auto-generated! 5 | 6 | It contains the basic startup code for a JUCE application. 7 | 8 | ============================================================================== 9 | */ 10 | 11 | #include "../JuceLibraryCode/JuceHeader.h" 12 | #include "MainComponent.h" 13 | 14 | //============================================================================== 15 | class aseSequencerProjectApplication : public JUCEApplication 16 | { 17 | public: 18 | //============================================================================== 19 | aseSequencerProjectApplication() {} 20 | 21 | const String getApplicationName() override { return ProjectInfo::projectName; } 22 | const String getApplicationVersion() override { return ProjectInfo::versionString; } 23 | bool moreThanOneInstanceAllowed() override { return true; } 24 | 25 | //============================================================================== 26 | void initialise (const String& commandLine) override 27 | { 28 | // This method is where you should put your application's initialisation code.. 29 | 30 | mainWindow.reset (new MainWindow (getApplicationName())); 31 | } 32 | 33 | void shutdown() override 34 | { 35 | // Add your application's shutdown code here.. 36 | 37 | mainWindow = nullptr; // (deletes our window) 38 | } 39 | 40 | //============================================================================== 41 | void systemRequestedQuit() override 42 | { 43 | // This is called when the app is being asked to quit: you can ignore this 44 | // request and let the app carry on running, or call quit() to allow the app to close. 45 | quit(); 46 | } 47 | 48 | void anotherInstanceStarted (const String& commandLine) override 49 | { 50 | // When another instance of the app is launched while this one is running, 51 | // this method is invoked, and the commandLine parameter tells you what 52 | // the other instance's command-line arguments were. 53 | } 54 | 55 | //============================================================================== 56 | /* 57 | This class implements the desktop window that contains an instance of 58 | our MainComponent class. 59 | */ 60 | class MainWindow : public DocumentWindow 61 | { 62 | public: 63 | MainWindow (String name) : DocumentWindow (name, 64 | Desktop::getInstance().getDefaultLookAndFeel() 65 | .findColour (ResizableWindow::backgroundColourId), 66 | DocumentWindow::allButtons) 67 | { 68 | setUsingNativeTitleBar (true); 69 | setContentOwned (new MainComponent(), true); 70 | 71 | #if JUCE_IOS || JUCE_ANDROID 72 | setFullScreen (true); 73 | #else 74 | setResizable (true, true); 75 | centreWithSize (getWidth(), getHeight()); 76 | #endif 77 | 78 | setVisible (true); 79 | } 80 | 81 | void closeButtonPressed() override 82 | { 83 | // This is called when the user tries to close this window. Here, we'll just 84 | // ask the app to quit when this happens, but you can change this to do 85 | // whatever you need. 86 | JUCEApplication::getInstance()->systemRequestedQuit(); 87 | } 88 | 89 | /* Note: Be careful if you override any DocumentWindow methods - the base 90 | class uses a lot of them, so by overriding you might break its functionality. 91 | It's best to do all your work in your content component instead, but if 92 | you really have to override any DocumentWindow methods, make sure your 93 | subclass also calls the superclass's method. 94 | */ 95 | 96 | private: 97 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainWindow) 98 | }; 99 | 100 | private: 101 | std::unique_ptr mainWindow; 102 | }; 103 | 104 | //============================================================================== 105 | // This macro generates the main() routine that launches the app. 106 | START_JUCE_APPLICATION (aseSequencerProjectApplication) 107 | -------------------------------------------------------------------------------- /Source/MainComponent.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | This file was auto-generated! 5 | 6 | ============================================================================== 7 | */ 8 | 9 | /* 10 | TO DO: 11 | 12 | -change play stop pause appearance 13 | -change duration slider colour 14 | 15 | */ 16 | #include "MainComponent.h" 17 | 18 | //============================================================================== 19 | MainComponent::MainComponent() : playButton("playButton", Colour(20, 31, 51), Colour(20, 31, 51), Colour(20, 31, 51)), 20 | stopButton("stopButton", Colour(20, 31, 51), Colour(20, 31, 51), Colour(20, 31, 51)), 21 | pauseButton("pauseButton", Colour(20, 31, 51), Colour(20, 31, 51), Colour(20, 31, 51)) 22 | { 23 | setSize (960, 400); 24 | timeCount = 0; 25 | beatCount = 0; 26 | bpm = 100; 27 | addAndMakeVisible(stepControls); 28 | addAndMakeVisible(noteControls); 29 | 30 | 31 | bpmSlider.setSliderStyle(Slider::LinearBar); 32 | //bpmSlider.setLookAndFeel(&customLookAndFeel); 33 | bpmSlider.setRange(0, 400, 1); 34 | bpmSlider.setValue(100); 35 | bpmSlider.addListener(this); 36 | addAndMakeVisible(bpmSlider); 37 | 38 | 39 | Rectangle buttonArea(40., 40.); 40 | 41 | Path playPath; 42 | playPath.addTriangle(0, 0, 0, buttonArea.getHeight(), buttonArea.getWidth() * sqrt(3) / 2, buttonArea.getHeight() / 2); 43 | playButton.setShape(playPath, false, true, true); 44 | playButton.setOnColours(Colours::darkred, Colours::darkred, Colours::darkred); 45 | playButton.shouldUseOnColours(true); 46 | playButton.setClickingTogglesState(true); 47 | playButton.addListener(this); 48 | addAndMakeVisible(playButton); 49 | 50 | Path pausePath; 51 | pausePath.addRectangle(0., 0., 13., buttonArea.getHeight()); 52 | pausePath.addRectangle(20., 0., 13., buttonArea.getHeight()); 53 | pauseButton.setShape(pausePath, false, true, true); 54 | pauseButton.setOnColours(Colour(8, 12, 20), Colour(8, 12, 20), Colour(8, 12, 20)); 55 | pauseButton.shouldUseOnColours(true); 56 | pauseButton.setClickingTogglesState(true); 57 | pauseButton.addListener(this); 58 | addAndMakeVisible(pauseButton); 59 | 60 | Path stopPath; 61 | stopPath.addRectangle(0., 0., buttonArea.getWidth(), buttonArea.getHeight()); 62 | stopButton.setShape(stopPath, false, true, true); 63 | stopButton.setOnColours(Colour(8, 12, 20), Colour(8, 12, 20), Colour(8, 12, 20)); 64 | stopButton.shouldUseOnColours(true); 65 | stopButton.setClickingTogglesState(true); 66 | stopButton.addListener(this); 67 | addAndMakeVisible(stopButton); 68 | 69 | saveButton.setButtonText("save"); 70 | saveButton.setLookAndFeel(&customLookAndFeel2); 71 | saveButton.setClickingTogglesState(true); 72 | saveButton.addListener(this); 73 | addAndMakeVisible(saveButton); 74 | 75 | for (int i = 0; i < 16; ++i) { 76 | 77 | 78 | for (int j = 0; j < 4; ++j) { 79 | 80 | pad[i][j].Button::setToggleState(false, dontSendNotification); 81 | pad[i][j].addListener(this); 82 | addAndMakeVisible(pad[i][j]); 83 | 84 | } 85 | } 86 | 87 | 88 | midiOutput->getDevices(); 89 | 90 | for (int i = 0; i < midiOutput->getDevices().size(); ++i) { 91 | deviceList.addItem(midiOutput->getDevices()[i], i+1); 92 | } 93 | 94 | deviceList.setLookAndFeel(&customLookAndFeel2); 95 | deviceList.addListener(this); 96 | addAndMakeVisible(deviceList); 97 | } 98 | 99 | MainComponent::~MainComponent() 100 | { 101 | 102 | } 103 | 104 | //============================================================================== 105 | void MainComponent::paint (Graphics& g) 106 | { 107 | // (Our component is opaque, so we must completely fill the background with a solid colour) 108 | g.fillAll(Colour(8, 12, 20)); 109 | 110 | g.setColour(Colour(12, 20, 31)); 111 | Rectangle toggleRect(120, 90, 830, 210); 112 | g.fillRoundedRectangle(toggleRect, 5.0f); 113 | g.fillRoundedRectangle(10.0f, 10.0f, 500.0f, 70.0f, 5.0f); 114 | g.fillRoundedRectangle(520.0f, 10.0f, 210.0f, 70.0f, 5.0f); 115 | g.fillRoundedRectangle(740.0f, 10.0f, 210.0f, 70.0f, 5.0f); 116 | 117 | g.setColour(Colour(223, 116, 12)); 118 | g.setFont(Font(14, 1)); 119 | g.drawText("Select MIDI device:", 50, 10, 200, 30.0f, Justification::centredLeft); 120 | g.drawText("B P M", 800, 10, 100, 30.0f, Justification::horizontallyCentred); 121 | g.drawText("Export pattern", 550, 10, 160, 30.0f, Justification::horizontallyCentred); 122 | 123 | } 124 | 125 | 126 | void MainComponent::resized() 127 | { 128 | juce::Rectangle area = getLocalBounds().reduced(10); 129 | 130 | stepControls.setBounds(120, 290, 1000, 100); 131 | noteControls.setBounds(10, 60, 200, 240); 132 | 133 | 134 | bpmSlider.setBounds(800, 40, 100, 20); //change bpm slider position 135 | 136 | int xx = 260; 137 | playButton.setBounds(xx, 25, 40, 40); 138 | pauseButton.setBounds(xx + 60, 25, 40, 40); 139 | stopButton.setBounds(xx + 130, 25, 40, 40); 140 | saveButton.setBounds(600, 40, 60, 20); 141 | 142 | deviceList.setBounds(50, 40, 100, 20); 143 | 144 | int x = 140; 145 | 146 | for (int i = 0; i < 16; ++i) { 147 | 148 | 149 | int y = 100; 150 | 151 | for (int j = 0; j < 4; ++j) { 152 | 153 | pad[i][j].setBounds(x, y, 40, 40); 154 | 155 | y += 50; 156 | } 157 | 158 | x += 50; 159 | } 160 | 161 | 162 | 163 | // This is called when the MainComponent is resized. 164 | // If you add any child components, this is where you should 165 | // update their positions. 166 | } 167 | 168 | void MainComponent::comboBoxChanged(ComboBox * comboBoxThatHasChanged) 169 | { 170 | if (comboBoxThatHasChanged = &deviceList) { 171 | 172 | if (midiOutput == nullptr && MidiOutput::getDevices().size() > 0) 173 | { 174 | midiOutput.reset(MidiOutput::openDevice(deviceList.getSelectedId())); 175 | } 176 | } 177 | 178 | } 179 | 180 | void MainComponent::sliderValueChanged(Slider * slider) 181 | { 182 | if (slider == &bpmSlider) { 183 | 184 | bpm = bpmSlider.getValue(); 185 | } 186 | 187 | } 188 | 189 | void MainComponent::buttonClicked(Button * button) 190 | { 191 | if (playButton.getToggleState() == true) { 192 | 193 | Timer::startTimer(60000 / (bpm * 4)); 194 | playButton.setRadioGroupId(1, dontSendNotification); 195 | midiEventList.begin(); 196 | } 197 | 198 | if (pauseButton.getToggleState() == true) { 199 | 200 | Timer::stopTimer(); 201 | pauseButton.setRadioGroupId(1, dontSendNotification); 202 | midiOutput->clearAllPendingMessages(); 203 | 204 | } 205 | 206 | if (stopButton.getToggleState() == true) { 207 | 208 | Timer::stopTimer(); 209 | stopButton.setRadioGroupId(1, dontSendNotification); 210 | 211 | for (int i = 0; i < 16; ++i) { 212 | for (int j = 0 ; j < 4; ++j) 213 | pad[i][j].resetStep(); 214 | } 215 | midiOutput->clearAllPendingMessages(); 216 | timeCount = 0; 217 | beatCount = 0; 218 | } 219 | 220 | if (saveButton.getToggleState() == true) { 221 | 222 | saveMidiPattern(); 223 | saveButton.setToggleState(false, dontSendNotification); 224 | } 225 | 226 | } 227 | 228 | void MainComponent::timerCallback() 229 | { 230 | 231 | if (beatCount > 15) { 232 | beatCount = 0; 233 | 234 | midiEventList.clear(); 235 | } 236 | 237 | 238 | for (track = 0; track < 4; ++track) { 239 | 240 | pad[beatCount][track].lightUpStep(); 241 | 242 | if (pad[beatCount][track].getToggleState() == true) { 243 | 244 | midiMessage = MidiMessage::noteOn(track + 1, noteControls.getTrackNote(track), stepControls.getVelValue(beatCount) / 127.0f); 245 | 246 | DBG(midiMessage.getDescription()); 247 | 248 | 249 | } 250 | 251 | else if (pad[beatCount][track].getToggleState() == false) { 252 | 253 | midiMessage = MidiMessage::noteOff(track + 1, noteControls.getTrackNote(track), stepControls.getVelValue(beatCount) / 127.0f); 254 | 255 | DBG(midiMessage.getDescription()); 256 | 257 | 258 | } 259 | 260 | midiOutput->sendMessageNow(midiMessage); 261 | } 262 | 263 | 264 | 265 | 266 | //GUI Grid 267 | if (beatCount == 0) { 268 | 269 | for (int i = 0; i < 4; ++i) 270 | pad[15][i].resetStep(); 271 | } 272 | 273 | else { 274 | 275 | for (int i = 0; i < 4; ++i) 276 | pad[beatCount - 1][i].resetStep(); 277 | } 278 | 279 | 280 | ++beatCount; 281 | ++timeCount; 282 | 283 | } 284 | 285 | void MainComponent::saveMidiPattern() 286 | {FileChooser chooser("Select file to save", 287 | File::getCurrentWorkingDirectory(), "*.mid"); 288 | 289 | if (chooser.browseForFileToSave(true)) 290 | { 291 | 292 | double timerInterval = 60000 / (bpm * 4); 293 | 294 | for (beatCount = 0; beatCount < 16; ++beatCount) { 295 | 296 | for (track = 0; track < 4; ++track) { 297 | 298 | if (pad[beatCount][track].getToggleState() == true) { 299 | 300 | midiMessage = MidiMessage::noteOn(track + 1, noteControls.getTrackNote(track), stepControls.getVelValue(beatCount) / 127.0f); 301 | 302 | midiMessage.setTimeStamp(timerInterval * (beatCount + 1)); 303 | midiEventList.addEvent(midiMessage); 304 | midiEventList.updateMatchedPairs(); 305 | DBG(midiMessage.getDescription()); 306 | DBG(midiMessage.getTimeStamp()); 307 | 308 | } 309 | 310 | else if (pad[beatCount][track].getToggleState() == false) { 311 | 312 | midiMessage = MidiMessage::noteOff(track + 1, noteControls.getTrackNote(track), stepControls.getVelValue(beatCount) / 127.0f); 313 | 314 | midiMessage.setTimeStamp(timerInterval * (beatCount + 1)); 315 | midiEventList.addEvent(midiMessage); 316 | midiEventList.updateMatchedPairs(); 317 | DBG(midiMessage.getDescription()); 318 | DBG(midiMessage.getTimeStamp()); 319 | } 320 | 321 | } 322 | } 323 | 324 | DBG(midiEventList.getNumEvents()); 325 | 326 | 327 | MidiFile midiFile; 328 | midiFile.setTicksPerQuarterNote(96); 329 | 330 | FileOutputStream outputStream(chooser.getResult()); 331 | 332 | midiFile.addTrack(midiEventList); 333 | midiFile.writeTo(outputStream, 0); 334 | outputStream.flush(); 335 | 336 | } 337 | } 338 | 339 | -------------------------------------------------------------------------------- /Source/MainComponent.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | This file was auto-generated! 5 | 6 | ============================================================================== 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "../JuceLibraryCode/JuceHeader.h" 12 | #include "TogglePad.h" 13 | #include "StepControls.h" 14 | #include "NoteControls.h" 15 | #include "CustomLookAndFeel.h" 16 | 17 | 18 | //============================================================================== 19 | /* 20 | This component lives inside our window, and this is where you should put all 21 | your controls and content. 22 | */ 23 | class MainComponent : public Component, 24 | public ComboBox::Listener, 25 | public Slider::Listener, 26 | public Button::Listener, 27 | private Timer 28 | 29 | { 30 | public: 31 | //============================================================================== 32 | MainComponent(); 33 | ~MainComponent(); 34 | 35 | //============================================================================== 36 | void paint (Graphics&) override; 37 | void resized() override; 38 | void comboBoxChanged(ComboBox * comboBoxThatHasChanged) override; 39 | void sliderValueChanged(Slider * slider) override; 40 | void buttonClicked(Button *button) override; 41 | void timerCallback() override; 42 | void saveMidiPattern(); 43 | 44 | 45 | private: 46 | //============================================================================== 47 | CustomLookAndFeel customLookAndFeel; 48 | CustomLookAndFeel2 customLookAndFeel2; 49 | 50 | TogglePad pad[16] [4]; 51 | 52 | TextButton saveButton; 53 | ShapeButton pauseButton, stopButton, playButton; 54 | Slider bpmSlider; 55 | StepControls stepControls; 56 | NoteControls noteControls; 57 | std::unique_ptr midiOutput; 58 | ComboBox deviceList; 59 | MidiMessage midiMessage; 60 | MidiMessageSequence midiEventList; 61 | MidiFile midiFile; 62 | 63 | unsigned int bpm; 64 | unsigned long int timeCount; 65 | int beatCount, track; 66 | 67 | 68 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainComponent) 69 | }; 70 | -------------------------------------------------------------------------------- /Source/NoteControls.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | NoteControls.cpp 5 | Created: 1 May 2019 2:25:49pm 6 | Author: Silvio 7 | 8 | ============================================================================== 9 | */ 10 | 11 | #include "../JuceLibraryCode/JuceHeader.h" 12 | #include "NoteControls.h" 13 | 14 | //============================================================================== 15 | NoteControls::NoteControls() 16 | { 17 | setSize(200, 240); 18 | 19 | noteMenu1.setLookAndFeel(&customLookAndFeel2); 20 | noteMenu2.setLookAndFeel(&customLookAndFeel2); 21 | noteMenu3.setLookAndFeel(&customLookAndFeel2); 22 | noteMenu4.setLookAndFeel(&customLookAndFeel2); 23 | noteMenu1.addListener(this); 24 | noteMenu2.addListener(this); 25 | noteMenu3.addListener(this); 26 | noteMenu4.addListener(this); 27 | 28 | { 29 | noteMenu1.addItem("C0", 24); 30 | noteMenu1.addItem("C#0", 25); 31 | noteMenu1.addItem("D0", 26); 32 | noteMenu1.addItem("D#0", 27); 33 | noteMenu1.addItem("E0", 28); 34 | noteMenu1.addItem("F0", 29); 35 | noteMenu1.addItem("F#0", 30); 36 | noteMenu1.addItem("G0", 31); 37 | noteMenu1.addItem("G#0", 32); 38 | noteMenu1.addItem("A0", 33); 39 | noteMenu1.addItem("A#0", 34); 40 | noteMenu1.addItem("B0", 35); 41 | 42 | noteMenu1.addItem("C1", 36); 43 | noteMenu1.addItem("C#1", 37); 44 | noteMenu1.addItem("D1", 38); 45 | noteMenu1.addItem("D#1", 39); 46 | noteMenu1.addItem("E1", 40); 47 | noteMenu1.addItem("F1", 41); 48 | noteMenu1.addItem("F#1", 42); 49 | noteMenu1.addItem("G1", 43); 50 | noteMenu1.addItem("G#1", 44); 51 | noteMenu1.addItem("A1", 45); 52 | noteMenu1.addItem("A#1", 46); 53 | noteMenu1.addItem("B1", 47); 54 | 55 | noteMenu1.addItem("C2", 48); 56 | noteMenu1.addItem("C#2", 49); 57 | noteMenu1.addItem("D2", 50); 58 | noteMenu1.addItem("D#2", 51); 59 | noteMenu1.addItem("E2", 52); 60 | noteMenu1.addItem("F2", 53); 61 | noteMenu1.addItem("F#2", 54); 62 | noteMenu1.addItem("G2", 55); 63 | noteMenu1.addItem("G#2", 56); 64 | noteMenu1.addItem("A2", 57); 65 | noteMenu1.addItem("A#2", 58); 66 | noteMenu1.addItem("B2", 59); 67 | 68 | noteMenu1.addItem("C3", 60); 69 | noteMenu1.addItem("C#3", 61); 70 | noteMenu1.addItem("D3", 62); 71 | noteMenu1.addItem("D#3", 63); 72 | noteMenu1.addItem("E3", 64); 73 | noteMenu1.addItem("F3", 65); 74 | noteMenu1.addItem("F#3", 66); 75 | noteMenu1.addItem("G3", 67); 76 | noteMenu1.addItem("G#3", 68); 77 | noteMenu1.addItem("A3", 69); 78 | noteMenu1.addItem("A#3", 70); 79 | noteMenu1.addItem("B3", 71); 80 | 81 | noteMenu1.addItem("C4", 72); 82 | noteMenu1.addItem("C#4", 73); 83 | noteMenu1.addItem("D4", 74); 84 | noteMenu1.addItem("D#4", 75); 85 | noteMenu1.addItem("E4", 76); 86 | noteMenu1.addItem("F4", 77); 87 | noteMenu1.addItem("F#4", 78); 88 | noteMenu1.addItem("G4", 79); 89 | noteMenu1.addItem("G#4", 80); 90 | noteMenu1.addItem("A4", 81); 91 | noteMenu1.addItem("A#4", 82); 92 | noteMenu1.addItem("B4", 83); 93 | 94 | noteMenu1.addItem("C5", 84); 95 | noteMenu1.addItem("C#5", 85); 96 | noteMenu1.addItem("D5", 86); 97 | noteMenu1.addItem("D#5", 87); 98 | noteMenu1.addItem("E5", 88); 99 | noteMenu1.addItem("F5", 89); 100 | noteMenu1.addItem("F#5", 90); 101 | noteMenu1.addItem("G5", 91); 102 | noteMenu1.addItem("G#5", 92); 103 | noteMenu1.addItem("A5", 93); 104 | noteMenu1.addItem("A#5", 94); 105 | noteMenu1.addItem("B5", 95); 106 | 107 | noteMenu1.addItem("C6", 96); 108 | noteMenu1.addItem("C#6", 97); 109 | noteMenu1.addItem("D6", 98); 110 | noteMenu1.addItem("D#6", 99); 111 | noteMenu1.addItem("E6", 100); 112 | noteMenu1.addItem("F6", 101); 113 | noteMenu1.addItem("F#6", 102); 114 | noteMenu1.addItem("G6", 103); 115 | noteMenu1.addItem("G#6", 104); 116 | noteMenu1.addItem("A6", 105); 117 | noteMenu1.addItem("A#6", 106); 118 | noteMenu1.addItem("B6", 107); 119 | 120 | 121 | addAndMakeVisible(noteMenu1); 122 | 123 | //============================================================================== 124 | //============================================================================== 125 | 126 | noteMenu2.addItem("C0", 24); 127 | noteMenu2.addItem("C#0", 25); 128 | noteMenu2.addItem("D0", 26); 129 | noteMenu2.addItem("D#0", 27); 130 | noteMenu2.addItem("E0", 28); 131 | noteMenu2.addItem("F0", 29); 132 | noteMenu2.addItem("F#0", 30); 133 | noteMenu2.addItem("G0", 31); 134 | noteMenu2.addItem("G#0", 32); 135 | noteMenu2.addItem("A0", 33); 136 | noteMenu2.addItem("A#0", 34); 137 | noteMenu2.addItem("B0", 35); 138 | 139 | noteMenu2.addItem("C1", 36); 140 | noteMenu2.addItem("C#1", 37); 141 | noteMenu2.addItem("D1", 38); 142 | noteMenu2.addItem("D#1", 39); 143 | noteMenu2.addItem("E1", 40); 144 | noteMenu2.addItem("F1", 41); 145 | noteMenu2.addItem("F#1", 42); 146 | noteMenu2.addItem("G1", 43); 147 | noteMenu2.addItem("G#1", 44); 148 | noteMenu2.addItem("A1", 45); 149 | noteMenu2.addItem("A#1", 46); 150 | noteMenu2.addItem("B1", 47); 151 | 152 | noteMenu2.addItem("C2", 48); 153 | noteMenu2.addItem("C#2", 49); 154 | noteMenu2.addItem("D2", 50); 155 | noteMenu2.addItem("D#2", 51); 156 | noteMenu2.addItem("E2", 52); 157 | noteMenu2.addItem("F2", 53); 158 | noteMenu2.addItem("F#2", 54); 159 | noteMenu2.addItem("G2", 55); 160 | noteMenu2.addItem("G#2", 56); 161 | noteMenu2.addItem("A2", 57); 162 | noteMenu2.addItem("A#2", 58); 163 | noteMenu2.addItem("B2", 59); 164 | 165 | noteMenu2.addItem("C3", 60); 166 | noteMenu2.addItem("C#3", 61); 167 | noteMenu2.addItem("D3", 62); 168 | noteMenu2.addItem("D#3", 63); 169 | noteMenu2.addItem("E3", 64); 170 | noteMenu2.addItem("F3", 65); 171 | noteMenu2.addItem("F#3", 66); 172 | noteMenu2.addItem("G3", 67); 173 | noteMenu2.addItem("G#3", 68); 174 | noteMenu2.addItem("A3", 69); 175 | noteMenu2.addItem("A#3", 70); 176 | noteMenu2.addItem("B3", 71); 177 | 178 | noteMenu2.addItem("C4", 72); 179 | noteMenu2.addItem("C#4", 73); 180 | noteMenu2.addItem("D4", 74); 181 | noteMenu2.addItem("D#4", 75); 182 | noteMenu2.addItem("E4", 76); 183 | noteMenu2.addItem("F4", 77); 184 | noteMenu2.addItem("F#4", 78); 185 | noteMenu2.addItem("G4", 79); 186 | noteMenu2.addItem("G#4", 80); 187 | noteMenu2.addItem("A4", 81); 188 | noteMenu2.addItem("A#4", 82); 189 | noteMenu2.addItem("B4", 83); 190 | 191 | noteMenu2.addItem("C5", 84); 192 | noteMenu2.addItem("C#5", 85); 193 | noteMenu2.addItem("D5", 86); 194 | noteMenu2.addItem("D#5", 87); 195 | noteMenu2.addItem("E5", 88); 196 | noteMenu2.addItem("F5", 89); 197 | noteMenu2.addItem("F#5", 90); 198 | noteMenu2.addItem("G5", 91); 199 | noteMenu2.addItem("G#5", 92); 200 | noteMenu2.addItem("A5", 93); 201 | noteMenu2.addItem("A#5", 94); 202 | noteMenu2.addItem("B5", 95); 203 | 204 | noteMenu2.addItem("C6", 96); 205 | noteMenu2.addItem("C#6", 97); 206 | noteMenu2.addItem("D6", 98); 207 | noteMenu2.addItem("D#6", 99); 208 | noteMenu2.addItem("E6", 100); 209 | noteMenu2.addItem("F6", 101); 210 | noteMenu2.addItem("F#6", 102); 211 | noteMenu2.addItem("G6", 103); 212 | noteMenu2.addItem("G#6", 104); 213 | noteMenu2.addItem("A6", 105); 214 | noteMenu2.addItem("A#6", 106); 215 | noteMenu2.addItem("B6", 107); 216 | 217 | addAndMakeVisible(noteMenu2); 218 | 219 | //============================================================================== 220 | //============================================================================== 221 | 222 | noteMenu3.addItem("C0", 24); 223 | noteMenu3.addItem("C#0", 25); 224 | noteMenu3.addItem("D0", 26); 225 | noteMenu3.addItem("D#0", 27); 226 | noteMenu3.addItem("E0", 28); 227 | noteMenu3.addItem("F0", 29); 228 | noteMenu3.addItem("F#0", 30); 229 | noteMenu3.addItem("G0", 31); 230 | noteMenu3.addItem("G#0", 32); 231 | noteMenu3.addItem("A0", 33); 232 | noteMenu3.addItem("A#0", 34); 233 | noteMenu3.addItem("B0", 35); 234 | 235 | noteMenu3.addItem("C1", 36); 236 | noteMenu3.addItem("C#1", 37); 237 | noteMenu3.addItem("D1", 38); 238 | noteMenu3.addItem("D#1", 39); 239 | noteMenu3.addItem("E1", 40); 240 | noteMenu3.addItem("F1", 41); 241 | noteMenu3.addItem("F#1", 42); 242 | noteMenu3.addItem("G1", 43); 243 | noteMenu3.addItem("G#1", 44); 244 | noteMenu3.addItem("A1", 45); 245 | noteMenu3.addItem("A#1", 46); 246 | noteMenu3.addItem("B1", 47); 247 | 248 | noteMenu3.addItem("C2", 48); 249 | noteMenu3.addItem("C#2", 49); 250 | noteMenu3.addItem("D2", 50); 251 | noteMenu3.addItem("D#2", 51); 252 | noteMenu3.addItem("E2", 52); 253 | noteMenu3.addItem("F2", 53); 254 | noteMenu3.addItem("F#2", 54); 255 | noteMenu3.addItem("G2", 55); 256 | noteMenu3.addItem("G#2", 56); 257 | noteMenu3.addItem("A2", 57); 258 | noteMenu3.addItem("A#2", 58); 259 | noteMenu3.addItem("B2", 59); 260 | 261 | noteMenu3.addItem("C3", 60); 262 | noteMenu3.addItem("C#3", 61); 263 | noteMenu3.addItem("D3", 62); 264 | noteMenu3.addItem("D#3", 63); 265 | noteMenu3.addItem("E3", 64); 266 | noteMenu3.addItem("F3", 65); 267 | noteMenu3.addItem("F#3", 66); 268 | noteMenu3.addItem("G3", 67); 269 | noteMenu3.addItem("G#3", 68); 270 | noteMenu3.addItem("A3", 69); 271 | noteMenu3.addItem("A#3", 70); 272 | noteMenu3.addItem("B3", 71); 273 | 274 | noteMenu3.addItem("C4", 72); 275 | noteMenu3.addItem("C#4", 73); 276 | noteMenu3.addItem("D4", 74); 277 | noteMenu3.addItem("D#4", 75); 278 | noteMenu3.addItem("E4", 76); 279 | noteMenu3.addItem("F4", 77); 280 | noteMenu3.addItem("F#4", 78); 281 | noteMenu3.addItem("G4", 79); 282 | noteMenu3.addItem("G#4", 80); 283 | noteMenu3.addItem("A4", 81); 284 | noteMenu3.addItem("A#4", 82); 285 | noteMenu3.addItem("B4", 83); 286 | 287 | noteMenu3.addItem("C5", 84); 288 | noteMenu3.addItem("C#5", 85); 289 | noteMenu3.addItem("D5", 86); 290 | noteMenu3.addItem("D#5", 87); 291 | noteMenu3.addItem("E5", 88); 292 | noteMenu3.addItem("F5", 89); 293 | noteMenu3.addItem("F#5", 90); 294 | noteMenu3.addItem("G5", 91); 295 | noteMenu3.addItem("G#5", 92); 296 | noteMenu3.addItem("A5", 93); 297 | noteMenu3.addItem("A#5", 94); 298 | noteMenu3.addItem("B5", 95); 299 | 300 | noteMenu3.addItem("C6", 96); 301 | noteMenu3.addItem("C#6", 97); 302 | noteMenu3.addItem("D6", 98); 303 | noteMenu3.addItem("D#6", 99); 304 | noteMenu3.addItem("E6", 100); 305 | noteMenu3.addItem("F6", 101); 306 | noteMenu3.addItem("F#6", 102); 307 | noteMenu3.addItem("G6", 103); 308 | noteMenu3.addItem("G#6", 104); 309 | noteMenu3.addItem("A6", 105); 310 | noteMenu3.addItem("A#6", 106); 311 | noteMenu3.addItem("B6", 107); 312 | 313 | addAndMakeVisible(noteMenu3); 314 | 315 | //============================================================================== 316 | //============================================================================== 317 | 318 | noteMenu4.addItem("C0", 24); 319 | noteMenu4.addItem("C#0", 25); 320 | noteMenu4.addItem("D0", 26); 321 | noteMenu4.addItem("D#0", 27); 322 | noteMenu4.addItem("E0", 28); 323 | noteMenu4.addItem("F0", 29); 324 | noteMenu4.addItem("F#0", 30); 325 | noteMenu4.addItem("G0", 31); 326 | noteMenu4.addItem("G#0", 32); 327 | noteMenu4.addItem("A0", 33); 328 | noteMenu4.addItem("A#0", 34); 329 | noteMenu4.addItem("B0", 35); 330 | 331 | noteMenu4.addItem("C1", 36); 332 | noteMenu4.addItem("C#1", 37); 333 | noteMenu4.addItem("D1", 38); 334 | noteMenu4.addItem("D#1", 39); 335 | noteMenu4.addItem("E1", 40); 336 | noteMenu4.addItem("F1", 41); 337 | noteMenu4.addItem("F#1", 42); 338 | noteMenu4.addItem("G1", 43); 339 | noteMenu4.addItem("G#1", 44); 340 | noteMenu4.addItem("A1", 45); 341 | noteMenu4.addItem("A#1", 46); 342 | noteMenu4.addItem("B1", 47); 343 | 344 | noteMenu4.addItem("C2", 48); 345 | noteMenu4.addItem("C#2", 49); 346 | noteMenu4.addItem("D2", 50); 347 | noteMenu4.addItem("D#2", 51); 348 | noteMenu4.addItem("E2", 52); 349 | noteMenu4.addItem("F2", 53); 350 | noteMenu4.addItem("F#2", 54); 351 | noteMenu4.addItem("G2", 55); 352 | noteMenu4.addItem("G#2", 56); 353 | noteMenu4.addItem("A2", 57); 354 | noteMenu4.addItem("A#2", 58); 355 | noteMenu4.addItem("B2", 59); 356 | 357 | noteMenu4.addItem("C3", 60); 358 | noteMenu4.addItem("C#3", 61); 359 | noteMenu4.addItem("D3", 62); 360 | noteMenu4.addItem("D#3", 63); 361 | noteMenu4.addItem("E3", 64); 362 | noteMenu4.addItem("F3", 65); 363 | noteMenu4.addItem("F#3", 66); 364 | noteMenu4.addItem("G3", 67); 365 | noteMenu4.addItem("G#3", 68); 366 | noteMenu4.addItem("A3", 69); 367 | noteMenu4.addItem("A#3", 70); 368 | noteMenu4.addItem("B3", 71); 369 | 370 | noteMenu4.addItem("C4", 72); 371 | noteMenu4.addItem("C#4", 73); 372 | noteMenu4.addItem("D4", 74); 373 | noteMenu4.addItem("D#4", 75); 374 | noteMenu4.addItem("E4", 76); 375 | noteMenu4.addItem("F4", 77); 376 | noteMenu4.addItem("F#4", 78); 377 | noteMenu4.addItem("G4", 79); 378 | noteMenu4.addItem("G#4", 80); 379 | noteMenu4.addItem("A4", 81); 380 | noteMenu4.addItem("A#4", 82); 381 | noteMenu4.addItem("B4", 83); 382 | 383 | noteMenu4.addItem("C5", 84); 384 | noteMenu4.addItem("C#5", 85); 385 | noteMenu4.addItem("D5", 86); 386 | noteMenu4.addItem("D#5", 87); 387 | noteMenu4.addItem("E5", 88); 388 | noteMenu4.addItem("F5", 89); 389 | noteMenu4.addItem("F#5", 90); 390 | noteMenu4.addItem("G5", 91); 391 | noteMenu4.addItem("G#5", 92); 392 | noteMenu4.addItem("A5", 93); 393 | noteMenu4.addItem("A#5", 94); 394 | noteMenu4.addItem("B5", 95); 395 | 396 | noteMenu4.addItem("C6", 96); 397 | noteMenu4.addItem("C#6", 97); 398 | noteMenu4.addItem("D6", 98); 399 | noteMenu4.addItem("D#6", 99); 400 | noteMenu4.addItem("E6", 100); 401 | noteMenu4.addItem("F6", 101); 402 | noteMenu4.addItem("F#6", 102); 403 | noteMenu4.addItem("G6", 103); 404 | noteMenu4.addItem("G#6", 104); 405 | noteMenu4.addItem("A6", 105); 406 | noteMenu4.addItem("A#6", 106); 407 | noteMenu4.addItem("B6", 107); 408 | 409 | addAndMakeVisible(noteMenu4); 410 | 411 | } 412 | //============================================================================== 413 | //============================================================================== 414 | 415 | noteMenu1.setSelectedId(60, dontSendNotification); 416 | noteMenu2.setSelectedId(60, dontSendNotification); 417 | noteMenu3.setSelectedId(60, dontSendNotification); 418 | noteMenu4.setSelectedId(60, dontSendNotification); 419 | } 420 | 421 | NoteControls::~NoteControls() 422 | { 423 | } 424 | 425 | void NoteControls::paint (Graphics& g) 426 | { 427 | juce::Rectangle area(0, 30, 100, 210); 428 | 429 | g.setColour(Colour(12, 20, 31)); 430 | g.fillRoundedRectangle(area, 5.0f); 431 | } 432 | 433 | void NoteControls::resized() 434 | { 435 | juce::Rectangle area = getLocalBounds(); 436 | 437 | noteMenu1.setBounds(20, 50, 60, 20); 438 | noteMenu2.setBounds(20, 100, 60, 20); 439 | noteMenu3.setBounds(20, 150, 60, 20); 440 | noteMenu4.setBounds(20, 200, 60, 20); 441 | } 442 | 443 | void NoteControls::comboBoxChanged(ComboBox * comboBoxThatHasChanged) 444 | { 445 | if (comboBoxThatHasChanged == ¬eMenu1) { 446 | 447 | trackNote[0] = noteMenu1.getSelectedId(); 448 | } 449 | 450 | if (comboBoxThatHasChanged == ¬eMenu2) { 451 | 452 | trackNote[1] = noteMenu2.getSelectedId(); 453 | } 454 | 455 | if (comboBoxThatHasChanged == ¬eMenu3) { 456 | 457 | trackNote[2] = noteMenu3.getSelectedId(); 458 | } 459 | 460 | if (comboBoxThatHasChanged == ¬eMenu4) { 461 | 462 | trackNote[3] = noteMenu4.getSelectedId(); 463 | } 464 | } 465 | 466 | int NoteControls::getTrackNote(int _track) 467 | { 468 | return trackNote[_track]; 469 | } 470 | -------------------------------------------------------------------------------- /Source/NoteControls.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | NoteControls.h 5 | Created: 1 May 2019 2:25:49pm 6 | Author: Silvio 7 | 8 | ============================================================================== 9 | */ 10 | 11 | #pragma once 12 | 13 | #include "../JuceLibraryCode/JuceHeader.h" 14 | #include "CustomLookAndFeel.h" 15 | //============================================================================== 16 | /* 17 | */ 18 | class NoteControls : public Component, 19 | public ComboBox::Listener 20 | { 21 | public: 22 | NoteControls(); 23 | ~NoteControls(); 24 | 25 | void paint (Graphics&) override; 26 | void resized() override; 27 | void comboBoxChanged(ComboBox * comboBoxThatHasChanged) override; 28 | int getTrackNote(int _track); 29 | 30 | private: 31 | 32 | CustomLookAndFeel2 customLookAndFeel2; 33 | ComboBox noteMenu1, noteMenu2, noteMenu3, noteMenu4; 34 | int trackNote[4] = { 60, 60, 60, 60 }; 35 | 36 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NoteControls) 37 | }; 38 | -------------------------------------------------------------------------------- /Source/StepControls.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | StepControls.cpp 5 | Created: 29 Apr 2019 5:14:46pm 6 | Author: Silvio 7 | 8 | ============================================================================== 9 | */ 10 | 11 | #include "../JuceLibraryCode/JuceHeader.h" 12 | #include "StepControls.h" 13 | 14 | //============================================================================== 15 | StepControls::StepControls() 16 | { 17 | // In your constructor, you should add any child components, and 18 | // initialise any special settings that your component needs. 19 | setSize(1000, 200); 20 | 21 | 22 | for (int i = 0; i < 16; ++i) { 23 | 24 | 25 | //velocity[i].setColour(Slider::thumbColourId, Colours::transparentBlack); 26 | //velocity[i].setColour(Slider::rotarySliderFillColourId, Colours::orange); 27 | //velocity[i].setColour(Slider::rotarySliderOutlineColourId, Colours::black); 28 | velocity[i].setSliderStyle(Slider::SliderStyle::RotaryVerticalDrag); 29 | velocity[i].setLookAndFeel(&customLookAndFeel); 30 | velocity[i].setRange(0.0f, 127.0f, 1.0f); 31 | velocity[i].setValue(64.0f); 32 | velocity[i].setTextBoxStyle(Slider::NoTextBox, true, 0, 0); 33 | velocity[i].addListener(this); 34 | addAndMakeVisible(velocity[i]); 35 | 36 | 37 | } 38 | } 39 | 40 | StepControls::~StepControls() 41 | { 42 | } 43 | 44 | void StepControls::paint (Graphics& g) 45 | { 46 | 47 | //juce::Rectangle titleArea(0, 10, getWidth(), 20); 48 | 49 | //g.fillAll(Colours::black); 50 | //g.setColour(Colours::white); 51 | //g.drawText("Filter", titleArea, Justification::centredTop); 52 | 53 | juce::Rectangle area(0, 20, 830, 80); 54 | 55 | g.setColour(Colour(12, 20, 31)); 56 | g.fillRoundedRectangle(area, 5.0f); 57 | 58 | } 59 | 60 | void StepControls::resized() 61 | { 62 | // This method is where you should set the bounds of any child 63 | // components that your component contains.. 64 | juce::Rectangle area = getLocalBounds().reduced(20); 65 | 66 | int x = 20; 67 | 68 | for (int i = 0; i < 16; ++i) { 69 | 70 | 71 | velocity[i].setBounds(x, area.getY() + 20, 40, 40); 72 | 73 | x += 50; 74 | } 75 | 76 | } 77 | 78 | void StepControls::sliderValueChanged(Slider * slider) 79 | { 80 | for (int i = 0; i < 16; ++i) { 81 | 82 | if (slider == &velocity[i]) { 83 | 84 | velValue[i] = velocity[i].getValue(); 85 | 86 | 87 | 88 | } 89 | } 90 | 91 | 92 | } 93 | 94 | 95 | float StepControls::getVelValue(int valuePlace) 96 | { 97 | return velValue[valuePlace]; 98 | } 99 | 100 | -------------------------------------------------------------------------------- /Source/StepControls.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | StepControls.h 5 | Created: 29 Apr 2019 5:14:46pm 6 | Author: Silvio 7 | 8 | ============================================================================== 9 | */ 10 | 11 | #pragma once 12 | 13 | #include "../JuceLibraryCode/JuceHeader.h" 14 | #include "CustomLookAndFeel.h" 15 | 16 | //============================================================================== 17 | /* 18 | */ 19 | class StepControls : public Component, 20 | public Slider::Listener 21 | { 22 | public: 23 | StepControls(); 24 | ~StepControls(); 25 | 26 | void paint (Graphics&) override; 27 | void resized() override; 28 | void sliderValueChanged(Slider * slider) override; 29 | float getVelValue(int valuePlace); 30 | //float getDurValue(int valuePlace); 31 | 32 | 33 | 34 | private: 35 | 36 | CustomLookAndFeel customLookAndFeel; 37 | Slider velocity[16], duration[16]; 38 | float velValue[16] = { 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 }; 39 | //float durValue[16] = { 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 }; 40 | 41 | 42 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (StepControls) 43 | }; 44 | -------------------------------------------------------------------------------- /Source/TogglePad.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | TogglePad.h 5 | Created: 29 Apr 2019 3:13:24pm 6 | Author: Silvio 7 | 8 | ============================================================================== 9 | */ 10 | 11 | #pragma once 12 | #include "../JuceLibraryCode/JuceHeader.h" 13 | 14 | class TogglePad : public ToggleButton 15 | 16 | { 17 | public: 18 | 19 | void paint(Graphics &g) 20 | { 21 | Colour fillColour = (getToggleState() == true ? Colour(223, 116, 12) : Colour(20, 31, 51)); 22 | g.fillAll(fillColour); 23 | 24 | if (getState() == highlight) { 25 | 26 | g.setOpacity(0.5); 27 | g.fillAll(); 28 | } 29 | 30 | 31 | } 32 | 33 | void lightUpStep() 34 | { 35 | setState(highlight); 36 | 37 | } 38 | 39 | void resetStep() 40 | { 41 | setState(ButtonState::buttonNormal); 42 | } 43 | 44 | private: 45 | ButtonState highlight; 46 | 47 | }; -------------------------------------------------------------------------------- /aseSequencer Project.jucer: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 10 | 11 | 12 | 13 | 15 | 16 | 18 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /doc/ASE Diagrams - UI Interface.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-gregorini003/juce-midi-step-sequencer/9c159deaad000c006780fef3d53267d72d9c1d3d/doc/ASE Diagrams - UI Interface.png -------------------------------------------------------------------------------- /doc/ASE Diagrams -Architecture Template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-gregorini003/juce-midi-step-sequencer/9c159deaad000c006780fef3d53267d72d9c1d3d/doc/ASE Diagrams -Architecture Template.png -------------------------------------------------------------------------------- /doc/ASE Diagrams -Class Diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-gregorini003/juce-midi-step-sequencer/9c159deaad000c006780fef3d53267d72d9c1d3d/doc/ASE Diagrams -Class Diagram.png -------------------------------------------------------------------------------- /doc/ASE Diagrams -Sequence Diagram 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-gregorini003/juce-midi-step-sequencer/9c159deaad000c006780fef3d53267d72d9c1d3d/doc/ASE Diagrams -Sequence Diagram 1.png -------------------------------------------------------------------------------- /doc/ASE Diagrams -Sequence Diagram 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-gregorini003/juce-midi-step-sequencer/9c159deaad000c006780fef3d53267d72d9c1d3d/doc/ASE Diagrams -Sequence Diagram 2.png -------------------------------------------------------------------------------- /doc/ASE Diagrams -Sequence Diagram 3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-gregorini003/juce-midi-step-sequencer/9c159deaad000c006780fef3d53267d72d9c1d3d/doc/ASE Diagrams -Sequence Diagram 3.png -------------------------------------------------------------------------------- /doc/ASE Diagrams -State Machine Diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-gregorini003/juce-midi-step-sequencer/9c159deaad000c006780fef3d53267d72d9c1d3d/doc/ASE Diagrams -State Machine Diagram.png -------------------------------------------------------------------------------- /doc/ASE Diagrams -Use Case Diagram 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-gregorini003/juce-midi-step-sequencer/9c159deaad000c006780fef3d53267d72d9c1d3d/doc/ASE Diagrams -Use Case Diagram 1.png -------------------------------------------------------------------------------- /doc/ASE Diagrams -Use Case Diagram 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-gregorini003/juce-midi-step-sequencer/9c159deaad000c006780fef3d53267d72d9c1d3d/doc/ASE Diagrams -Use Case Diagram 2.png -------------------------------------------------------------------------------- /doc/ASE_Gregorini_Silvio_Journal.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-gregorini003/juce-midi-step-sequencer/9c159deaad000c006780fef3d53267d72d9c1d3d/doc/ASE_Gregorini_Silvio_Journal.pdf -------------------------------------------------------------------------------- /doc/screenshot-sequencer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-gregorini003/juce-midi-step-sequencer/9c159deaad000c006780fef3d53267d72d9c1d3d/doc/screenshot-sequencer.png --------------------------------------------------------------------------------