diff --git a/Examples/MPEIntroductionTutorial.zip b/Examples/MPEIntroductionTutorial.zip new file mode 100644 index 0000000..f6ce5b0 Binary files /dev/null and b/Examples/MPEIntroductionTutorial.zip differ diff --git a/Examples/MPEIntroductionTutorial/JuceLibraryCode/JuceHeader.h b/Examples/MPEIntroductionTutorial/JuceLibraryCode/JuceHeader.h new file mode 100644 index 0000000..927a43b --- /dev/null +++ b/Examples/MPEIntroductionTutorial/JuceLibraryCode/JuceHeader.h @@ -0,0 +1,52 @@ +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + + This is the header file that your files should include in order to get all the + JUCE library headers. You should avoid including the JUCE headers directly in + your own source files, because that wouldn't pick up the correct configuration + options for your app. + +*/ + +#pragma once + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#if defined (JUCE_PROJUCER_VERSION) && JUCE_PROJUCER_VERSION < JUCE_VERSION + /** If you've hit this error then the version of the Projucer that was used to generate this project is + older than the version of the JUCE modules being included. To fix this error, re-save your project + using the latest version of the Projucer or, if you aren't using the Projucer to manage your project, + remove the JUCE_PROJUCER_VERSION define. + */ + #error "This project was last saved using an outdated version of the Projucer! Re-save this project with the latest version to fix this error." +#endif + +#if ! DONT_SET_USING_JUCE_NAMESPACE + // If your code uses a lot of JUCE classes, then this will obviously save you + // a lot of typing, but can be disabled by setting DONT_SET_USING_JUCE_NAMESPACE. + using namespace juce; +#endif + +#if ! JUCE_DONT_DECLARE_PROJECTINFO +namespace ProjectInfo +{ + const char* const projectName = "MPEIntroductionTutorial"; + const char* const companyName = "JUCE"; + const char* const versionString = "1.0.0"; + const int versionNumber = 0x10000; +} +#endif diff --git a/Examples/MPEIntroductionTutorial/JuceLibraryCode/ReadMe.txt b/Examples/MPEIntroductionTutorial/JuceLibraryCode/ReadMe.txt new file mode 100644 index 0000000..1e6784f --- /dev/null +++ b/Examples/MPEIntroductionTutorial/JuceLibraryCode/ReadMe.txt @@ -0,0 +1,12 @@ + + Important Note!! + ================ + +The purpose of this folder is to contain files that are auto-generated by the Projucer, +and ALL files in this folder will be mercilessly DELETED and completely re-written whenever +the Projucer saves your project. + +Therefore, it's a bad idea to make any manual changes to the files in here, or to +put any of your own files in here if you don't want to lose them. (Of course you may choose +to add the folder's contents to your version-control system so that you can re-merge your own +modifications after the Projucer has saved its changes). diff --git a/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_audio_basics.cpp b/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_audio_basics.cpp new file mode 100644 index 0000000..4070844 --- /dev/null +++ b/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_audio_basics.cpp @@ -0,0 +1,8 @@ +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + +*/ + +#include diff --git a/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_audio_basics.mm b/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_audio_basics.mm new file mode 100644 index 0000000..0c09914 --- /dev/null +++ b/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_audio_basics.mm @@ -0,0 +1,8 @@ +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + +*/ + +#include diff --git a/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_audio_devices.cpp b/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_audio_devices.cpp new file mode 100644 index 0000000..c9c2d11 --- /dev/null +++ b/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_audio_devices.cpp @@ -0,0 +1,8 @@ +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + +*/ + +#include diff --git a/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_audio_devices.mm b/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_audio_devices.mm new file mode 100644 index 0000000..77e69b1 --- /dev/null +++ b/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_audio_devices.mm @@ -0,0 +1,8 @@ +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + +*/ + +#include diff --git a/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_audio_formats.cpp b/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_audio_formats.cpp new file mode 100644 index 0000000..78e74f7 --- /dev/null +++ b/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_audio_formats.cpp @@ -0,0 +1,8 @@ +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + +*/ + +#include diff --git a/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_audio_formats.mm b/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_audio_formats.mm new file mode 100644 index 0000000..0adf319 --- /dev/null +++ b/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_audio_formats.mm @@ -0,0 +1,8 @@ +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + +*/ + +#include diff --git a/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_audio_processors.cpp b/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_audio_processors.cpp new file mode 100644 index 0000000..0dbc0b6 --- /dev/null +++ b/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_audio_processors.cpp @@ -0,0 +1,8 @@ +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + +*/ + +#include diff --git a/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_audio_processors.mm b/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_audio_processors.mm new file mode 100644 index 0000000..dac7f37 --- /dev/null +++ b/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_audio_processors.mm @@ -0,0 +1,8 @@ +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + +*/ + +#include diff --git a/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_audio_processors_ara.cpp b/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_audio_processors_ara.cpp new file mode 100644 index 0000000..1651fc5 --- /dev/null +++ b/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_audio_processors_ara.cpp @@ -0,0 +1,8 @@ +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + +*/ + +#include diff --git a/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_audio_processors_lv2_libs.cpp b/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_audio_processors_lv2_libs.cpp new file mode 100644 index 0000000..1151b5a --- /dev/null +++ b/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_audio_processors_lv2_libs.cpp @@ -0,0 +1,8 @@ +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + +*/ + +#include diff --git a/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_audio_utils.cpp b/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_audio_utils.cpp new file mode 100644 index 0000000..f31e8b6 --- /dev/null +++ b/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_audio_utils.cpp @@ -0,0 +1,8 @@ +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + +*/ + +#include diff --git a/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_audio_utils.mm b/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_audio_utils.mm new file mode 100644 index 0000000..4dfd5b4 --- /dev/null +++ b/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_audio_utils.mm @@ -0,0 +1,8 @@ +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + +*/ + +#include diff --git a/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_core.cpp b/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_core.cpp new file mode 100644 index 0000000..6f55178 --- /dev/null +++ b/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_core.cpp @@ -0,0 +1,8 @@ +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + +*/ + +#include diff --git a/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_core.mm b/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_core.mm new file mode 100644 index 0000000..db83b69 --- /dev/null +++ b/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_core.mm @@ -0,0 +1,8 @@ +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + +*/ + +#include diff --git a/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_core_CompilationTime.cpp b/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_core_CompilationTime.cpp new file mode 100644 index 0000000..789042d --- /dev/null +++ b/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_core_CompilationTime.cpp @@ -0,0 +1,8 @@ +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + +*/ + +#include diff --git a/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_data_structures.cpp b/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_data_structures.cpp new file mode 100644 index 0000000..f53f241 --- /dev/null +++ b/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_data_structures.cpp @@ -0,0 +1,8 @@ +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + +*/ + +#include diff --git a/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_data_structures.mm b/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_data_structures.mm new file mode 100644 index 0000000..db212c9 --- /dev/null +++ b/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_data_structures.mm @@ -0,0 +1,8 @@ +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + +*/ + +#include diff --git a/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_events.cpp b/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_events.cpp new file mode 100644 index 0000000..33a3a69 --- /dev/null +++ b/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_events.cpp @@ -0,0 +1,8 @@ +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + +*/ + +#include diff --git a/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_events.mm b/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_events.mm new file mode 100644 index 0000000..6ad0eda --- /dev/null +++ b/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_events.mm @@ -0,0 +1,8 @@ +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + +*/ + +#include diff --git a/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_graphics.cpp b/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_graphics.cpp new file mode 100644 index 0000000..12f6750 --- /dev/null +++ b/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_graphics.cpp @@ -0,0 +1,8 @@ +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + +*/ + +#include diff --git a/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_graphics.mm b/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_graphics.mm new file mode 100644 index 0000000..ab22eb4 --- /dev/null +++ b/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_graphics.mm @@ -0,0 +1,8 @@ +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + +*/ + +#include diff --git a/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_graphics_Harfbuzz.cpp b/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_graphics_Harfbuzz.cpp new file mode 100644 index 0000000..419cf23 --- /dev/null +++ b/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_graphics_Harfbuzz.cpp @@ -0,0 +1,8 @@ +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + +*/ + +#include diff --git a/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_graphics_Sheenbidi.c b/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_graphics_Sheenbidi.c new file mode 100644 index 0000000..df5eb4b --- /dev/null +++ b/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_graphics_Sheenbidi.c @@ -0,0 +1,8 @@ +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + +*/ + +#include diff --git a/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_gui_basics.cpp b/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_gui_basics.cpp new file mode 100644 index 0000000..80a5878 --- /dev/null +++ b/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_gui_basics.cpp @@ -0,0 +1,8 @@ +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + +*/ + +#include diff --git a/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_gui_basics.mm b/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_gui_basics.mm new file mode 100644 index 0000000..708837c --- /dev/null +++ b/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_gui_basics.mm @@ -0,0 +1,8 @@ +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + +*/ + +#include diff --git a/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_gui_extra.cpp b/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_gui_extra.cpp new file mode 100644 index 0000000..ea050e5 --- /dev/null +++ b/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_gui_extra.cpp @@ -0,0 +1,8 @@ +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + +*/ + +#include diff --git a/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_gui_extra.mm b/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_gui_extra.mm new file mode 100644 index 0000000..9bb3fea --- /dev/null +++ b/Examples/MPEIntroductionTutorial/JuceLibraryCode/include_juce_gui_extra.mm @@ -0,0 +1,8 @@ +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + +*/ + +#include diff --git a/Examples/MPEIntroductionTutorial/MPEIntroductionTutorial.jucer b/Examples/MPEIntroductionTutorial/MPEIntroductionTutorial.jucer new file mode 100644 index 0000000..72c46c6 --- /dev/null +++ b/Examples/MPEIntroductionTutorial/MPEIntroductionTutorial.jucer @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Examples/MPEIntroductionTutorial/Source/MPEIntroductionTutorial.h b/Examples/MPEIntroductionTutorial/Source/MPEIntroductionTutorial.h new file mode 100644 index 0000000..167dba8 --- /dev/null +++ b/Examples/MPEIntroductionTutorial/Source/MPEIntroductionTutorial.h @@ -0,0 +1,536 @@ +/* + ============================================================================== + + This file is part of the JUCE tutorials. + Copyright (c) 2020 - Raw Material Software Limited + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, + WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR + PURPOSE, ARE DISCLAIMED. + + ============================================================================== +*/ + +/******************************************************************************* + The block below describes the properties of this PIP. A PIP is a short snippet + of code that can be read by the Projucer and used to generate a JUCE project. + + BEGIN_JUCE_PIP_METADATA + + name: MPEIntroductionTutorial + version: 1.0.0 + vendor: JUCE + website: http://juce.com + description: Synthesiser using MPE specifications. + + dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats, + juce_audio_processors, juce_audio_utils, juce_core, + juce_data_structures, juce_events, juce_graphics, + juce_gui_basics, juce_gui_extra + exporters: xcode_mac, vs2019, linux_make + + type: Component + mainClass: MainComponent + + useLocalCopy: 1 + + END_JUCE_PIP_METADATA + +*******************************************************************************/ + + +#pragma once + +//============================================================================== +class NoteComponent : public juce::Component +{ +public: + NoteComponent (const juce::MPENote& n) + : note (n), colour (juce::Colours::white) + {} + + //============================================================================== + void update (const juce::MPENote& newNote, juce::Point newCentre) + { + note = newNote; + centre = newCentre; + + setBounds (getSquareAroundCentre (juce::jmax (getNoteOnRadius(), getNoteOffRadius(), getPressureRadius())) + .getUnion (getTextRectangle()) + .getSmallestIntegerContainer() + .expanded (3)); + + repaint(); + } + + //============================================================================== + void paint (juce::Graphics& g) override + { + if (note.keyState == juce::MPENote::keyDown || note.keyState == juce::MPENote::keyDownAndSustained) + drawPressedNoteCircle (g, colour); + else if (note.keyState == juce::MPENote::sustained) + drawSustainedNoteCircle (g, colour); + else + return; + + drawNoteLabel (g, colour); + } + + //============================================================================== + juce::MPENote note; + juce::Colour colour; + juce::Point centre; + +private: + //============================================================================== + void drawPressedNoteCircle (juce::Graphics& g, juce::Colour zoneColour) + { + g.setColour (zoneColour.withAlpha (0.3f)); + g.fillEllipse (translateToLocalBounds (getSquareAroundCentre (getNoteOnRadius()))); + g.setColour (zoneColour); + g.drawEllipse (translateToLocalBounds (getSquareAroundCentre (getPressureRadius())), 2.0f); + } + + //============================================================================== + void drawSustainedNoteCircle (juce::Graphics& g, juce::Colour zoneColour) + { + g.setColour (zoneColour); + juce::Path circle, dashedCircle; + circle.addEllipse (translateToLocalBounds (getSquareAroundCentre (getNoteOffRadius()))); + const float dashLengths[] = { 3.0f, 3.0f }; + juce::PathStrokeType (2.0, juce::PathStrokeType::mitered).createDashedStroke (dashedCircle, circle, dashLengths, 2); + g.fillPath (dashedCircle); + } + + //============================================================================== + void drawNoteLabel (juce::Graphics& g, juce::Colour) + { + auto textBounds = translateToLocalBounds (getTextRectangle()).getSmallestIntegerContainer(); + g.drawText ("+", textBounds, juce::Justification::centred); + g.drawText (juce::MidiMessage::getMidiNoteName (note.initialNote, true, true, 3), textBounds, juce::Justification::centredBottom); + g.setFont (juce::Font (22.0f, juce::Font::bold)); + g.drawText (juce::String (note.midiChannel), textBounds, juce::Justification::centredTop); + } + + //============================================================================== + juce::Rectangle getSquareAroundCentre (float radius) const noexcept + { + return juce::Rectangle (radius * 2.0f, radius * 2.0f).withCentre (centre); + } + + juce::Rectangle translateToLocalBounds (juce::Rectangle r) const noexcept + { + return r - getPosition().toFloat(); + } + + juce::Rectangle getTextRectangle() const noexcept + { + return juce::Rectangle (30.0f, 50.0f).withCentre (centre); + } + + float getNoteOnRadius() const noexcept { return note.noteOnVelocity.asUnsignedFloat() * maxNoteRadius; } + float getNoteOffRadius() const noexcept { return note.noteOffVelocity.asUnsignedFloat() * maxNoteRadius; } + float getPressureRadius() const noexcept { return note.pressure.asUnsignedFloat() * maxNoteRadius; } + + static constexpr auto maxNoteRadius = 100.0f; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NoteComponent) +}; + +//============================================================================== +class Visualiser : public juce::Component, + public juce::MPEInstrument::Listener, + private juce::AsyncUpdater +{ +public: + //============================================================================== + Visualiser() {} + + //============================================================================== + void paint (juce::Graphics& g) override + { + g.fillAll (juce::Colours::black); + + auto noteDistance = float (getWidth()) / 128; + + for (auto i = 0; i < 128; ++i) + { + auto x = noteDistance * (float) i; + auto noteHeight = int (juce::MidiMessage::isMidiNoteBlack (i) ? 0.7 * getHeight() : getHeight()); + g.setColour (juce::MidiMessage::isMidiNoteBlack (i) ? juce::Colours::white : juce::Colours::grey); + g.drawLine (x, 0.0f, x, (float) noteHeight); + + if (i > 0 && i % 12 == 0) + { + g.setColour (juce::Colours::grey); + auto octaveNumber = (i / 12) - 2; + g.drawText ("C" + juce::String (octaveNumber), (int) x - 15, getHeight() - 30, 30, 30, juce::Justification::centredBottom); + } + } + } + + //============================================================================== + void noteAdded (juce::MPENote newNote) override + { + const juce::ScopedLock sl (lock); + activeNotes.add (newNote); + triggerAsyncUpdate(); + } + + void notePressureChanged (juce::MPENote note) override { noteChanged (note); } + void notePitchbendChanged (juce::MPENote note) override { noteChanged (note); } + void noteTimbreChanged (juce::MPENote note) override { noteChanged (note); } + void noteKeyStateChanged (juce::MPENote note) override { noteChanged (note); } + + void noteChanged (juce::MPENote changedNote) + { + const juce::ScopedLock sl (lock); + + for (auto& note : activeNotes) + if (note.noteID == changedNote.noteID) + note = changedNote; + + triggerAsyncUpdate(); + } + + void noteReleased (juce::MPENote finishedNote) override + { + const juce::ScopedLock sl (lock); + + for (auto i = activeNotes.size(); --i >= 0;) + if (activeNotes.getReference(i).noteID == finishedNote.noteID) + activeNotes.remove (i); + + triggerAsyncUpdate(); + } + + +private: + //============================================================================== + const juce::MPENote* findActiveNote (int noteID) const noexcept + { + for (auto& note : activeNotes) + if (note.noteID == noteID) + return ¬e; + + return nullptr; + } + + NoteComponent* findNoteComponent (int noteID) const noexcept + { + for (auto& noteComp : noteComponents) + if (noteComp->note.noteID == noteID) + return noteComp; + + return nullptr; + } + + //============================================================================== + void handleAsyncUpdate() override + { + const juce::ScopedLock sl (lock); + + for (auto i = noteComponents.size(); --i >= 0;) + if (findActiveNote (noteComponents.getUnchecked(i)->note.noteID) == nullptr) + noteComponents.remove (i); + + for (auto& note : activeNotes) + if (findNoteComponent (note.noteID) == nullptr) + addAndMakeVisible (noteComponents.add (new NoteComponent (note))); + + for (auto& noteComp : noteComponents) + if (auto* noteInfo = findActiveNote (noteComp->note.noteID)) + noteComp->update (*noteInfo, getCentrePositionForNote (*noteInfo)); + } + + //============================================================================== + juce::Point getCentrePositionForNote (juce::MPENote note) const + { + auto n = float (note.initialNote) + float (note.totalPitchbendInSemitones); + auto x = (float) getWidth() * n / 128; + auto y = (float) getHeight() * (1 - note.timbre.asUnsignedFloat()); + + return { x, y }; + } + + //============================================================================== + juce::OwnedArray noteComponents; + juce::CriticalSection lock; + juce::Array activeNotes; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Visualiser) +}; + +//============================================================================== +class MPEDemoSynthVoice : public juce::MPESynthesiserVoice +{ +public: + //============================================================================== + MPEDemoSynthVoice() {} + + //============================================================================== + void noteStarted() override + { + jassert (currentlyPlayingNote.isValid()); + jassert (currentlyPlayingNote.keyState == juce::MPENote::keyDown + || currentlyPlayingNote.keyState == juce::MPENote::keyDownAndSustained); + + // get data from the current MPENote + level .setTargetValue (currentlyPlayingNote.pressure.asUnsignedFloat()); + frequency.setTargetValue (currentlyPlayingNote.getFrequencyInHertz()); + timbre .setTargetValue (currentlyPlayingNote.timbre.asUnsignedFloat()); + + phase = 0.0; + auto cyclesPerSample = frequency.getNextValue() / currentSampleRate; + phaseDelta = 2.0 * juce::MathConstants::pi * cyclesPerSample; + + tailOff = 0.0; + } + + void noteStopped (bool allowTailOff) override + { + jassert (currentlyPlayingNote.keyState == juce::MPENote::off); + + if (allowTailOff) + { + // start a tail-off by setting this flag. The render callback will pick up on + // this and do a fade out, calling clearCurrentNote() when it's finished. + + if (tailOff == 0.0) // we only need to begin a tail-off if it's not already doing so - the + // stopNote method could be called more than once. + tailOff = 1.0; + } + else + { + // we're being told to stop playing immediately, so reset everything.. + clearCurrentNote(); + phaseDelta = 0.0; + } + } + + void notePressureChanged() override + { + level.setTargetValue (currentlyPlayingNote.pressure.asUnsignedFloat()); + } + + void notePitchbendChanged() override + { + frequency.setTargetValue (currentlyPlayingNote.getFrequencyInHertz()); + } + + void noteTimbreChanged() override + { + timbre.setTargetValue (currentlyPlayingNote.timbre.asUnsignedFloat()); + } + + void noteKeyStateChanged() override {} + + void setCurrentSampleRate (double newRate) override + { + if (! juce::approximatelyEqual (currentSampleRate, newRate)) + { + noteStopped (false); + currentSampleRate = newRate; + + level .reset (currentSampleRate, smoothingLengthInSeconds); + timbre .reset (currentSampleRate, smoothingLengthInSeconds); + frequency.reset (currentSampleRate, smoothingLengthInSeconds); + } + } + + //============================================================================== + void renderNextBlock (juce::AudioBuffer& outputBuffer, + int startSample, + int numSamples) override + { + if (phaseDelta != 0.0) + { + if (tailOff > 0.0) + { + while (--numSamples >= 0) + { + auto currentSample = getNextSample() * (float) tailOff; + + for (auto i = outputBuffer.getNumChannels(); --i >= 0;) + outputBuffer.addSample (i, startSample, currentSample); + + ++startSample; + + tailOff *= 0.99; + + if (tailOff <= 0.005) + { + clearCurrentNote(); + + phaseDelta = 0.0; + break; + } + } + } + else + { + while (--numSamples >= 0) + { + auto currentSample = getNextSample(); + + for (auto i = outputBuffer.getNumChannels(); --i >= 0;) + outputBuffer.addSample (i, startSample, currentSample); + + ++startSample; + } + } + } + } + +private: + //============================================================================== + float getNextSample() noexcept + { + auto levelDb = (level.getNextValue() - 1.0) * maxLevelDb; + auto amplitude = std::pow (10.0f, 0.05f * levelDb) * maxLevel; + + // timbre is used to blend between a sine and a square. + auto f1 = std::sin (phase); + auto f2 = std::copysign (1.0, f1); + auto a2 = timbre.getNextValue(); + auto a1 = 1.0 - a2; + + auto nextSample = float (amplitude * ((a1 * f1) + (a2 * f2))); + + auto cyclesPerSample = frequency.getNextValue() / currentSampleRate; + phaseDelta = 2.0 * juce::MathConstants::pi * cyclesPerSample; + phase = std::fmod (phase + phaseDelta, 2.0 * juce::MathConstants::pi); + + return nextSample; + } + + //============================================================================== + juce::SmoothedValue level, timbre, frequency; + + double phase = 0.0; + double phaseDelta = 0.0; + double tailOff = 0.0; + + // some useful constants + static constexpr auto maxLevel = 0.05; + static constexpr auto maxLevelDb = 31.0; + static constexpr auto smoothingLengthInSeconds = 0.01; +}; + +//============================================================================== +class MainComponent : public juce::Component, + private juce::AudioIODeviceCallback, // [1] + private juce::MidiInputCallback // [2] +{ +public: + //============================================================================== + MainComponent() + : audioSetupComp (audioDeviceManager, 0, 0, 0, 256, + true, // showMidiInputOptions must be true + true, true, false) + { + audioDeviceManager.initialise (0, 2, nullptr, true, {}, nullptr); + audioDeviceManager.addMidiInputDeviceCallback ({}, this); // [6] + audioDeviceManager.addAudioCallback (this); + + addAndMakeVisible (audioSetupComp); + addAndMakeVisible (visualiserViewport); + + visualiserViewport.setScrollBarsShown (false, true); + visualiserViewport.setViewedComponent (&visualiserComp, false); + visualiserViewport.setViewPositionProportionately (0.5, 0.0); + + visualiserInstrument.addListener (&visualiserComp); + + for (auto i = 0; i < 15; ++i) + synth.addVoice (new MPEDemoSynthVoice()); + + synth.enableLegacyMode (24); + synth.setVoiceStealingEnabled (false); + + visualiserInstrument.enableLegacyMode (24); + + setSize (650, 560); + } + + ~MainComponent() override + { + audioDeviceManager.removeMidiInputDeviceCallback ({}, this); + audioDeviceManager.removeAudioCallback (this); + } + + //============================================================================== + void resized() override + { + auto visualiserCompWidth = 2800; + auto visualiserCompHeight = 300; + + auto r = getLocalBounds(); + + visualiserViewport.setBounds (r.removeFromBottom (visualiserCompHeight)); + visualiserComp.setBounds ({ visualiserCompWidth, + visualiserViewport.getHeight() - visualiserViewport.getScrollBarThickness() }); + + audioSetupComp.setBounds (r); + } + + //============================================================================== + void audioDeviceIOCallbackWithContext (const float* const* /*inputChannelData*/, + int /*numInputChannels*/, + float* const* outputChannelData, + int numOutputChannels, + int numSamples, + const juce::AudioIODeviceCallbackContext& /*context*/) override + { + // make buffer + juce::AudioBuffer buffer (outputChannelData, numOutputChannels, numSamples); + + // clear it to silence + buffer.clear(); + + juce::MidiBuffer incomingMidi; + + // get the MIDI messages for this audio block + midiCollector.removeNextBlockOfMessages (incomingMidi, numSamples); + + // synthesise the block + synth.renderNextBlock (buffer, incomingMidi, 0, numSamples); + } + + void audioDeviceAboutToStart (juce::AudioIODevice* device) override + { + auto sampleRate = device->getCurrentSampleRate(); + midiCollector.reset (sampleRate); + synth.setCurrentPlaybackSampleRate (sampleRate); + } + + void audioDeviceStopped() override {} + +private: + //============================================================================== + void handleIncomingMidiMessage (juce::MidiInput* /*source*/, + const juce::MidiMessage& message) override + { + visualiserInstrument.processNextMidiEvent (message); + midiCollector.addMessageToQueue (message); + } + + //============================================================================== + juce::AudioDeviceManager audioDeviceManager; // [3] + juce::AudioDeviceSelectorComponent audioSetupComp; // [4] + + Visualiser visualiserComp; + juce::Viewport visualiserViewport; + + juce::MPEInstrument visualiserInstrument; + juce::MPESynthesiser synth; + juce::MidiMessageCollector midiCollector; // [5] + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainComponent) +}; diff --git a/Examples/MPEIntroductionTutorial/Source/Main.cpp b/Examples/MPEIntroductionTutorial/Source/Main.cpp new file mode 100644 index 0000000..12364ba --- /dev/null +++ b/Examples/MPEIntroductionTutorial/Source/Main.cpp @@ -0,0 +1,96 @@ +/* + ============================================================================== + + This file contains the startup code for a PIP. + + ============================================================================== +*/ + +#include +#include "MPEIntroductionTutorial.h" + +class Application : public juce::JUCEApplication +{ +public: + //============================================================================== + Application() = default; + + const juce::String getApplicationName() override { return "MPEIntroductionTutorial"; } + const juce::String getApplicationVersion() override { return "1.0.0"; } + + void initialise (const juce::String&) override + { + mainWindow.reset (new MainWindow ("MPEIntroductionTutorial", std::make_unique(), *this)); + } + + void shutdown() override { mainWindow = nullptr; } + +private: + class MainWindow : public juce::DocumentWindow + { + public: + MainWindow (const juce::String& name, std::unique_ptr c, JUCEApplication& a) + : DocumentWindow (name, juce::Desktop::getInstance().getDefaultLookAndFeel() + .findColour (ResizableWindow::backgroundColourId), + juce::DocumentWindow::allButtons), + app (a) + { + setUsingNativeTitleBar (true); + + #if JUCE_ANDROID || JUCE_IOS + setContentOwned (new SafeAreaComponent { std::move (c) }, true); + setFullScreen (true); + #else + setContentOwned (c.release(), true); + setResizable (true, false); + setResizeLimits (300, 250, 10000, 10000); + centreWithSize (getWidth(), getHeight()); + #endif + + setVisible (true); + } + + void closeButtonPressed() override + { + app.systemRequestedQuit(); + } + + #if JUCE_ANDROID || JUCE_IOS + class SafeAreaComponent : public juce::Component + { + public: + explicit SafeAreaComponent (std::unique_ptr c) + : content (std::move (c)) + { + addAndMakeVisible (*content); + } + + void resized() override + { + if (const auto* d = Desktop::getInstance().getDisplays().getDisplayForRect (getLocalBounds())) + content->setBounds (d->safeAreaInsets.subtractedFrom (getLocalBounds())); + } + + private: + std::unique_ptr content; + }; + + void parentSizeChanged() override + { + if (auto* c = getContentComponent()) + c->resized(); + } + #endif + + private: + JUCEApplication& app; + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainWindow) + }; + + std::unique_ptr mainWindow; +}; + +//============================================================================== +START_JUCE_APPLICATION (Application) diff --git a/Examples/WavetableSynthTutorial.zip b/Examples/WavetableSynthTutorial.zip new file mode 100644 index 0000000..bf8ae67 Binary files /dev/null and b/Examples/WavetableSynthTutorial.zip differ diff --git a/Examples/WavetableSynthTutorial/JuceLibraryCode/JuceHeader.h b/Examples/WavetableSynthTutorial/JuceLibraryCode/JuceHeader.h new file mode 100644 index 0000000..0b4b794 --- /dev/null +++ b/Examples/WavetableSynthTutorial/JuceLibraryCode/JuceHeader.h @@ -0,0 +1,52 @@ +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + + This is the header file that your files should include in order to get all the + JUCE library headers. You should avoid including the JUCE headers directly in + your own source files, because that wouldn't pick up the correct configuration + options for your app. + +*/ + +#pragma once + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#if defined (JUCE_PROJUCER_VERSION) && JUCE_PROJUCER_VERSION < JUCE_VERSION + /** If you've hit this error then the version of the Projucer that was used to generate this project is + older than the version of the JUCE modules being included. To fix this error, re-save your project + using the latest version of the Projucer or, if you aren't using the Projucer to manage your project, + remove the JUCE_PROJUCER_VERSION define. + */ + #error "This project was last saved using an outdated version of the Projucer! Re-save this project with the latest version to fix this error." +#endif + +#if ! DONT_SET_USING_JUCE_NAMESPACE + // If your code uses a lot of JUCE classes, then this will obviously save you + // a lot of typing, but can be disabled by setting DONT_SET_USING_JUCE_NAMESPACE. + using namespace juce; +#endif + +#if ! JUCE_DONT_DECLARE_PROJECTINFO +namespace ProjectInfo +{ + const char* const projectName = "WavetableSynthTutorial"; + const char* const companyName = "JUCE"; + const char* const versionString = "1.0.0"; + const int versionNumber = 0x10000; +} +#endif diff --git a/Examples/WavetableSynthTutorial/JuceLibraryCode/ReadMe.txt b/Examples/WavetableSynthTutorial/JuceLibraryCode/ReadMe.txt new file mode 100644 index 0000000..1e6784f --- /dev/null +++ b/Examples/WavetableSynthTutorial/JuceLibraryCode/ReadMe.txt @@ -0,0 +1,12 @@ + + Important Note!! + ================ + +The purpose of this folder is to contain files that are auto-generated by the Projucer, +and ALL files in this folder will be mercilessly DELETED and completely re-written whenever +the Projucer saves your project. + +Therefore, it's a bad idea to make any manual changes to the files in here, or to +put any of your own files in here if you don't want to lose them. (Of course you may choose +to add the folder's contents to your version-control system so that you can re-merge your own +modifications after the Projucer has saved its changes). diff --git a/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_audio_basics.cpp b/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_audio_basics.cpp new file mode 100644 index 0000000..4070844 --- /dev/null +++ b/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_audio_basics.cpp @@ -0,0 +1,8 @@ +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + +*/ + +#include diff --git a/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_audio_basics.mm b/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_audio_basics.mm new file mode 100644 index 0000000..0c09914 --- /dev/null +++ b/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_audio_basics.mm @@ -0,0 +1,8 @@ +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + +*/ + +#include diff --git a/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_audio_devices.cpp b/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_audio_devices.cpp new file mode 100644 index 0000000..c9c2d11 --- /dev/null +++ b/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_audio_devices.cpp @@ -0,0 +1,8 @@ +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + +*/ + +#include diff --git a/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_audio_devices.mm b/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_audio_devices.mm new file mode 100644 index 0000000..77e69b1 --- /dev/null +++ b/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_audio_devices.mm @@ -0,0 +1,8 @@ +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + +*/ + +#include diff --git a/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_audio_formats.cpp b/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_audio_formats.cpp new file mode 100644 index 0000000..78e74f7 --- /dev/null +++ b/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_audio_formats.cpp @@ -0,0 +1,8 @@ +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + +*/ + +#include diff --git a/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_audio_formats.mm b/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_audio_formats.mm new file mode 100644 index 0000000..0adf319 --- /dev/null +++ b/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_audio_formats.mm @@ -0,0 +1,8 @@ +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + +*/ + +#include diff --git a/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_audio_processors.cpp b/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_audio_processors.cpp new file mode 100644 index 0000000..0dbc0b6 --- /dev/null +++ b/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_audio_processors.cpp @@ -0,0 +1,8 @@ +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + +*/ + +#include diff --git a/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_audio_processors.mm b/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_audio_processors.mm new file mode 100644 index 0000000..dac7f37 --- /dev/null +++ b/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_audio_processors.mm @@ -0,0 +1,8 @@ +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + +*/ + +#include diff --git a/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_audio_processors_ara.cpp b/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_audio_processors_ara.cpp new file mode 100644 index 0000000..1651fc5 --- /dev/null +++ b/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_audio_processors_ara.cpp @@ -0,0 +1,8 @@ +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + +*/ + +#include diff --git a/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_audio_processors_lv2_libs.cpp b/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_audio_processors_lv2_libs.cpp new file mode 100644 index 0000000..1151b5a --- /dev/null +++ b/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_audio_processors_lv2_libs.cpp @@ -0,0 +1,8 @@ +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + +*/ + +#include diff --git a/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_audio_utils.cpp b/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_audio_utils.cpp new file mode 100644 index 0000000..f31e8b6 --- /dev/null +++ b/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_audio_utils.cpp @@ -0,0 +1,8 @@ +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + +*/ + +#include diff --git a/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_audio_utils.mm b/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_audio_utils.mm new file mode 100644 index 0000000..4dfd5b4 --- /dev/null +++ b/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_audio_utils.mm @@ -0,0 +1,8 @@ +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + +*/ + +#include diff --git a/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_core.cpp b/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_core.cpp new file mode 100644 index 0000000..6f55178 --- /dev/null +++ b/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_core.cpp @@ -0,0 +1,8 @@ +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + +*/ + +#include diff --git a/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_core.mm b/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_core.mm new file mode 100644 index 0000000..db83b69 --- /dev/null +++ b/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_core.mm @@ -0,0 +1,8 @@ +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + +*/ + +#include diff --git a/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_core_CompilationTime.cpp b/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_core_CompilationTime.cpp new file mode 100644 index 0000000..789042d --- /dev/null +++ b/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_core_CompilationTime.cpp @@ -0,0 +1,8 @@ +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + +*/ + +#include diff --git a/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_data_structures.cpp b/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_data_structures.cpp new file mode 100644 index 0000000..f53f241 --- /dev/null +++ b/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_data_structures.cpp @@ -0,0 +1,8 @@ +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + +*/ + +#include diff --git a/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_data_structures.mm b/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_data_structures.mm new file mode 100644 index 0000000..db212c9 --- /dev/null +++ b/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_data_structures.mm @@ -0,0 +1,8 @@ +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + +*/ + +#include diff --git a/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_events.cpp b/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_events.cpp new file mode 100644 index 0000000..33a3a69 --- /dev/null +++ b/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_events.cpp @@ -0,0 +1,8 @@ +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + +*/ + +#include diff --git a/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_events.mm b/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_events.mm new file mode 100644 index 0000000..6ad0eda --- /dev/null +++ b/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_events.mm @@ -0,0 +1,8 @@ +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + +*/ + +#include diff --git a/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_graphics.cpp b/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_graphics.cpp new file mode 100644 index 0000000..12f6750 --- /dev/null +++ b/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_graphics.cpp @@ -0,0 +1,8 @@ +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + +*/ + +#include diff --git a/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_graphics.mm b/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_graphics.mm new file mode 100644 index 0000000..ab22eb4 --- /dev/null +++ b/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_graphics.mm @@ -0,0 +1,8 @@ +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + +*/ + +#include diff --git a/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_graphics_Harfbuzz.cpp b/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_graphics_Harfbuzz.cpp new file mode 100644 index 0000000..419cf23 --- /dev/null +++ b/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_graphics_Harfbuzz.cpp @@ -0,0 +1,8 @@ +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + +*/ + +#include diff --git a/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_graphics_Sheenbidi.c b/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_graphics_Sheenbidi.c new file mode 100644 index 0000000..df5eb4b --- /dev/null +++ b/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_graphics_Sheenbidi.c @@ -0,0 +1,8 @@ +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + +*/ + +#include diff --git a/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_gui_basics.cpp b/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_gui_basics.cpp new file mode 100644 index 0000000..80a5878 --- /dev/null +++ b/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_gui_basics.cpp @@ -0,0 +1,8 @@ +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + +*/ + +#include diff --git a/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_gui_basics.mm b/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_gui_basics.mm new file mode 100644 index 0000000..708837c --- /dev/null +++ b/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_gui_basics.mm @@ -0,0 +1,8 @@ +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + +*/ + +#include diff --git a/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_gui_extra.cpp b/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_gui_extra.cpp new file mode 100644 index 0000000..ea050e5 --- /dev/null +++ b/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_gui_extra.cpp @@ -0,0 +1,8 @@ +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + +*/ + +#include diff --git a/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_gui_extra.mm b/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_gui_extra.mm new file mode 100644 index 0000000..9bb3fea --- /dev/null +++ b/Examples/WavetableSynthTutorial/JuceLibraryCode/include_juce_gui_extra.mm @@ -0,0 +1,8 @@ +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + +*/ + +#include diff --git a/Examples/WavetableSynthTutorial/Source/Main.cpp b/Examples/WavetableSynthTutorial/Source/Main.cpp new file mode 100644 index 0000000..08c890c --- /dev/null +++ b/Examples/WavetableSynthTutorial/Source/Main.cpp @@ -0,0 +1,96 @@ +/* + ============================================================================== + + This file contains the startup code for a PIP. + + ============================================================================== +*/ + +#include +#include "WavetableSynthTutorial_01.h" + +class Application : public juce::JUCEApplication +{ +public: + //============================================================================== + Application() = default; + + const juce::String getApplicationName() override { return "WavetableSynthTutorial"; } + const juce::String getApplicationVersion() override { return "1.0.0"; } + + void initialise (const juce::String&) override + { + mainWindow.reset (new MainWindow ("WavetableSynthTutorial", std::make_unique(), *this)); + } + + void shutdown() override { mainWindow = nullptr; } + +private: + class MainWindow : public juce::DocumentWindow + { + public: + MainWindow (const juce::String& name, std::unique_ptr c, JUCEApplication& a) + : DocumentWindow (name, juce::Desktop::getInstance().getDefaultLookAndFeel() + .findColour (ResizableWindow::backgroundColourId), + juce::DocumentWindow::allButtons), + app (a) + { + setUsingNativeTitleBar (true); + + #if JUCE_ANDROID || JUCE_IOS + setContentOwned (new SafeAreaComponent { std::move (c) }, true); + setFullScreen (true); + #else + setContentOwned (c.release(), true); + setResizable (true, false); + setResizeLimits (300, 250, 10000, 10000); + centreWithSize (getWidth(), getHeight()); + #endif + + setVisible (true); + } + + void closeButtonPressed() override + { + app.systemRequestedQuit(); + } + + #if JUCE_ANDROID || JUCE_IOS + class SafeAreaComponent : public juce::Component + { + public: + explicit SafeAreaComponent (std::unique_ptr c) + : content (std::move (c)) + { + addAndMakeVisible (*content); + } + + void resized() override + { + if (const auto* d = Desktop::getInstance().getDisplays().getDisplayForRect (getLocalBounds())) + content->setBounds (d->safeAreaInsets.subtractedFrom (getLocalBounds())); + } + + private: + std::unique_ptr content; + }; + + void parentSizeChanged() override + { + if (auto* c = getContentComponent()) + c->resized(); + } + #endif + + private: + JUCEApplication& app; + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainWindow) + }; + + std::unique_ptr mainWindow; +}; + +//============================================================================== +START_JUCE_APPLICATION (Application) diff --git a/Examples/WavetableSynthTutorial/Source/WavetableSynthTutorial_01.h b/Examples/WavetableSynthTutorial/Source/WavetableSynthTutorial_01.h new file mode 100644 index 0000000..ff24303 --- /dev/null +++ b/Examples/WavetableSynthTutorial/Source/WavetableSynthTutorial_01.h @@ -0,0 +1,163 @@ +/* + ============================================================================== + + This file is part of the JUCE tutorials. + Copyright (c) 2020 - Raw Material Software Limited + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, + WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR + PURPOSE, ARE DISCLAIMED. + + ============================================================================== +*/ + +/******************************************************************************* + The block below describes the properties of this PIP. A PIP is a short snippet + of code that can be read by the Projucer and used to generate a JUCE project. + + BEGIN_JUCE_PIP_METADATA + + name: WavetableSynthTutorial + version: 1.0.0 + vendor: JUCE + website: http://juce.com + description: Wavetable synthesiser. + + dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats, + juce_audio_processors, juce_audio_utils, juce_core, + juce_data_structures, juce_events, juce_graphics, + juce_gui_basics, juce_gui_extra + exporters: xcode_mac, vs2019, linux_make + + type: Component + mainClass: MainContentComponent + + useLocalCopy: 1 + + END_JUCE_PIP_METADATA + +*******************************************************************************/ + + +#pragma once + +//============================================================================== +class SineOscillator +{ +public: + SineOscillator() {} + + void setFrequency (float frequency, float sampleRate) + { + auto cyclesPerSample = frequency / sampleRate; + angleDelta = cyclesPerSample * juce::MathConstants::twoPi; + } + + forcedinline void updateAngle() noexcept + { + currentAngle += angleDelta; + if (currentAngle >= juce::MathConstants::twoPi) + currentAngle -= juce::MathConstants::twoPi; + } + + forcedinline float getNextSample() noexcept + { + auto currentSample = std::sin (currentAngle); + updateAngle(); + return currentSample; + } + +private: + float currentAngle = 0.0f, angleDelta = 0.0f; +}; + +//============================================================================== +class MainContentComponent : public juce::AudioAppComponent, + public juce::Timer +{ +public: + MainContentComponent() + { + cpuUsageLabel.setText ("CPU Usage", juce::dontSendNotification); + cpuUsageText.setJustificationType (juce::Justification::right); + addAndMakeVisible (cpuUsageLabel); + addAndMakeVisible (cpuUsageText); + + setSize (400, 200); + setAudioChannels (0, 2); // no inputs, two outputs + startTimer (50); + } + + ~MainContentComponent() override + { + shutdownAudio(); + } + + void resized() override + { + cpuUsageLabel.setBounds (10, 10, getWidth() - 20, 20); + cpuUsageText .setBounds (10, 10, getWidth() - 20, 20); + } + + void timerCallback() override + { + auto cpu = deviceManager.getCpuUsage() * 100; + cpuUsageText.setText (juce::String (cpu, 6) + " %", juce::dontSendNotification); + } + + void prepareToPlay (int, double sampleRate) override + { + auto numberOfOscillators = 200; // [1] + + for (auto i = 0; i < numberOfOscillators; ++i) + { + auto* oscillator = new SineOscillator(); // [2] + + auto midiNote = juce::Random::getSystemRandom().nextDouble() * 36.0 + 48.0; // [3] + auto frequency = 440.0 * pow (2.0, (midiNote - 69.0) / 12.0); // [4] + + oscillator->setFrequency ((float) frequency, (float) sampleRate); // [5] + oscillators.add (oscillator); + } + + level = 0.25f / (float) numberOfOscillators; // [6] + } + + void releaseResources() override {} + + void getNextAudioBlock (const juce::AudioSourceChannelInfo& bufferToFill) override + { + auto* leftBuffer = bufferToFill.buffer->getWritePointer (0, bufferToFill.startSample); // [7] + auto* rightBuffer = bufferToFill.buffer->getWritePointer (1, bufferToFill.startSample); + + bufferToFill.clearActiveBufferRegion(); + + for (auto oscillatorIndex = 0; oscillatorIndex < oscillators.size(); ++oscillatorIndex) + { + auto* oscillator = oscillators.getUnchecked (oscillatorIndex); // [8] + + for (auto sample = 0; sample < bufferToFill.numSamples; ++sample) + { + auto levelSample = oscillator->getNextSample() * level; // [9] + + leftBuffer[sample] += levelSample; // [10] + rightBuffer[sample] += levelSample; + } + } + } + +private: + juce::Label cpuUsageLabel; + juce::Label cpuUsageText; + + float level = 0.0f; + juce::OwnedArray oscillators; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainContentComponent) +}; diff --git a/Examples/WavetableSynthTutorial/Source/WavetableSynthTutorial_02.h b/Examples/WavetableSynthTutorial/Source/WavetableSynthTutorial_02.h new file mode 100644 index 0000000..0a538da --- /dev/null +++ b/Examples/WavetableSynthTutorial/Source/WavetableSynthTutorial_02.h @@ -0,0 +1,196 @@ +/* + ============================================================================== + + This file is part of the JUCE tutorials. + Copyright (c) 2020 - Raw Material Software Limited + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, + WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR + PURPOSE, ARE DISCLAIMED. + + ============================================================================== +*/ + +/******************************************************************************* + The block below describes the properties of this PIP. A PIP is a short snippet + of code that can be read by the Projucer and used to generate a JUCE project. + + BEGIN_JUCE_PIP_METADATA + + name: WavetableSynthTutorial + version: 2.0.0 + vendor: JUCE + website: http://juce.com + description: Wavetable synthesiser. + + dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats, + juce_audio_processors, juce_audio_utils, juce_core, + juce_data_structures, juce_events, juce_graphics, + juce_gui_basics, juce_gui_extra + exporters: xcode_mac, vs2019, linux_make + + type: Component + mainClass: MainContentComponent + + useLocalCopy: 1 + + END_JUCE_PIP_METADATA + +*******************************************************************************/ + + +#pragma once + +//============================================================================== +class WavetableOscillator +{ +public: + WavetableOscillator (const juce::AudioSampleBuffer& wavetableToUse) + : wavetable (wavetableToUse) + { + jassert (wavetable.getNumChannels() == 1); + } + + void setFrequency (float frequency, float sampleRate) + { + auto tableSizeOverSampleRate = (float) wavetable.getNumSamples() / sampleRate; + tableDelta = frequency * tableSizeOverSampleRate; + } + + forcedinline float getNextSample() noexcept + { + auto tableSize = (unsigned int) wavetable.getNumSamples(); + + auto index0 = (unsigned int) currentIndex; // [6] + auto index1 = index0 == (tableSize - 1) ? (unsigned int) 0 : index0 + 1; + + auto frac = currentIndex - (float) index0; // [7] + + auto* table = wavetable.getReadPointer (0); // [8] + auto value0 = table[index0]; + auto value1 = table[index1]; + + auto currentSample = value0 + frac * (value1 - value0); // [9] + + if ((currentIndex += tableDelta) > (float) tableSize) // [10] + currentIndex -= (float) tableSize; + + return currentSample; + } + +private: + const juce::AudioSampleBuffer& wavetable; + float currentIndex = 0.0f, tableDelta = 0.0f; +}; + +//============================================================================== +class MainContentComponent : public juce::AudioAppComponent, + public juce::Timer +{ +public: + MainContentComponent() + { + cpuUsageLabel.setText ("CPU Usage", juce::dontSendNotification); + cpuUsageText.setJustificationType (juce::Justification::right); + addAndMakeVisible (cpuUsageLabel); + addAndMakeVisible (cpuUsageText); + + createWavetable(); + + setSize (400, 200); + setAudioChannels (0, 2); // no inputs, two outputs + startTimer (50); + } + + ~MainContentComponent() override + { + shutdownAudio(); + } + + void resized() override + { + cpuUsageLabel.setBounds (10, 10, getWidth() - 20, 20); + cpuUsageText .setBounds (10, 10, getWidth() - 20, 20); + } + + void timerCallback() override + { + auto cpu = deviceManager.getCpuUsage() * 100; + cpuUsageText.setText (juce::String (cpu, 6) + " %", juce::dontSendNotification); + } + + void createWavetable() + { + sineTable.setSize (1, (int) tableSize); + auto* samples = sineTable.getWritePointer (0); // [3] + + auto angleDelta = juce::MathConstants::twoPi / (double) (tableSize - 1); // [4] + auto currentAngle = 0.0; + + for (unsigned int i = 0; i < tableSize; ++i) + { + auto sample = std::sin (currentAngle); // [5] + samples[i] = (float) sample; + currentAngle += angleDelta; + } + } + + void prepareToPlay (int, double sampleRate) override + { + auto numberOfOscillators = 200; + + for (auto i = 0; i < numberOfOscillators; ++i) + { + auto* oscillator = new WavetableOscillator (sineTable); + + auto midiNote = juce::Random::getSystemRandom().nextDouble() * 36.0 + 48.0; + auto frequency = 440.0 * pow (2.0, (midiNote - 69.0) / 12.0); + + oscillator->setFrequency ((float) frequency, (float) sampleRate); + oscillators.add (oscillator); + } + + level = 0.25f / (float) numberOfOscillators; + } + + void releaseResources() override {} + + void getNextAudioBlock (const juce::AudioSourceChannelInfo& bufferToFill) override + { + auto* leftBuffer = bufferToFill.buffer->getWritePointer (0, bufferToFill.startSample); + auto* rightBuffer = bufferToFill.buffer->getWritePointer (1, bufferToFill.startSample); + + bufferToFill.clearActiveBufferRegion(); + + for (auto oscillatorIndex = 0; oscillatorIndex < oscillators.size(); ++oscillatorIndex) + { + auto* oscillator = oscillators.getUnchecked (oscillatorIndex); + + for (auto sample = 0; sample < bufferToFill.numSamples; ++sample) + { + auto levelSample = oscillator->getNextSample() * level; + + leftBuffer[sample] += levelSample; + rightBuffer[sample] += levelSample; + } + } + } + +private: + juce::Label cpuUsageLabel; + juce::Label cpuUsageText; + + const unsigned int tableSize = 1 << 7; // [2] + float level = 0.0f; + + juce::AudioSampleBuffer sineTable; // [1] + juce::OwnedArray oscillators; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainContentComponent) +}; diff --git a/Examples/WavetableSynthTutorial/Source/WavetableSynthTutorial_03.h b/Examples/WavetableSynthTutorial/Source/WavetableSynthTutorial_03.h new file mode 100644 index 0000000..f6de098 --- /dev/null +++ b/Examples/WavetableSynthTutorial/Source/WavetableSynthTutorial_03.h @@ -0,0 +1,198 @@ +/* + ============================================================================== + + This file is part of the JUCE tutorials. + Copyright (c) 2020 - Raw Material Software Limited + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, + WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR + PURPOSE, ARE DISCLAIMED. + + ============================================================================== +*/ + +/******************************************************************************* + The block below describes the properties of this PIP. A PIP is a short snippet + of code that can be read by the Projucer and used to generate a JUCE project. + + BEGIN_JUCE_PIP_METADATA + + name: WavetableSynthTutorial + version: 3.0.0 + vendor: JUCE + website: http://juce.com + description: Wavetable synthesiser. + + dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats, + juce_audio_processors, juce_audio_utils, juce_core, + juce_data_structures, juce_events, juce_graphics, + juce_gui_basics, juce_gui_extra + exporters: xcode_mac, vs2019, linux_make + + type: Component + mainClass: MainContentComponent + + useLocalCopy: 1 + + END_JUCE_PIP_METADATA + +*******************************************************************************/ + + +#pragma once + +//============================================================================== +class WavetableOscillator +{ +public: + WavetableOscillator (const juce::AudioSampleBuffer& wavetableToUse) + : wavetable (wavetableToUse), + tableSize (wavetable.getNumSamples() - 1) + { + jassert (wavetable.getNumChannels() == 1); + } + + void setFrequency (float frequency, float sampleRate) + { + auto tableSizeOverSampleRate = (float) tableSize / sampleRate; + tableDelta = frequency * tableSizeOverSampleRate; + } + + forcedinline float getNextSample() noexcept + { + auto index0 = (unsigned int) currentIndex; + auto index1 = index0 + 1; + + auto frac = currentIndex - (float) index0; + + auto* table = wavetable.getReadPointer (0); + auto value0 = table[index0]; + auto value1 = table[index1]; + + auto currentSample = value0 + frac * (value1 - value0); + + if ((currentIndex += tableDelta) > (float) tableSize) + currentIndex -= (float) tableSize; + + return currentSample; + } + +private: + const juce::AudioSampleBuffer& wavetable; + const int tableSize; + float currentIndex = 0.0f, tableDelta = 0.0f; +}; + +//============================================================================== +class MainContentComponent : public juce::AudioAppComponent, + public juce::Timer +{ +public: + MainContentComponent() + { + cpuUsageLabel.setText ("CPU Usage", juce::dontSendNotification); + cpuUsageText.setJustificationType (juce::Justification::right); + addAndMakeVisible (cpuUsageLabel); + addAndMakeVisible (cpuUsageText); + + createWavetable(); + + setSize (400, 200); + setAudioChannels (0, 2); // no inputs, two outputs + startTimer (50); + } + + ~MainContentComponent() override + { + shutdownAudio(); + } + + void resized() override + { + cpuUsageLabel.setBounds (10, 10, getWidth() - 20, 20); + cpuUsageText .setBounds (10, 10, getWidth() - 20, 20); + } + + void timerCallback() override + { + auto cpu = deviceManager.getCpuUsage() * 100; + cpuUsageText.setText (juce::String (cpu, 6) + " %", juce::dontSendNotification); + } + + void createWavetable() + { + sineTable.setSize (1, (int) tableSize + 1); + auto* samples = sineTable.getWritePointer (0); + + auto angleDelta = juce::MathConstants::twoPi / (double) (tableSize - 1); + auto currentAngle = 0.0; + + for (unsigned int i = 0; i < tableSize; ++i) + { + auto sample = std::sin (currentAngle); + samples[i] = (float) sample; + currentAngle += angleDelta; + } + + samples[tableSize] = samples[0]; + } + + void prepareToPlay (int, double sampleRate) override + { + auto numberOfOscillators = 200; + + for (auto i = 0; i < numberOfOscillators; ++i) + { + auto* oscillator = new WavetableOscillator (sineTable); + + auto midiNote = juce::Random::getSystemRandom().nextDouble() * 36.0 + 48.0; + auto frequency = 440.0 * pow (2.0, (midiNote - 69.0) / 12.0); + + oscillator->setFrequency ((float) frequency, (float) sampleRate); + oscillators.add (oscillator); + } + + level = 0.25f / (float) numberOfOscillators; + } + + void releaseResources() override {} + + void getNextAudioBlock (const juce::AudioSourceChannelInfo& bufferToFill) override + { + auto* leftBuffer = bufferToFill.buffer->getWritePointer (0, bufferToFill.startSample); + auto* rightBuffer = bufferToFill.buffer->getWritePointer (1, bufferToFill.startSample); + + bufferToFill.clearActiveBufferRegion(); + + for (auto oscillatorIndex = 0; oscillatorIndex < oscillators.size(); ++oscillatorIndex) + { + auto* oscillator = oscillators.getUnchecked (oscillatorIndex); + + for (auto sample = 0; sample < bufferToFill.numSamples; ++sample) + { + auto levelSample = oscillator->getNextSample() * level; + + leftBuffer[sample] += levelSample; + rightBuffer[sample] += levelSample; + } + } + } + +private: + juce::Label cpuUsageLabel; + juce::Label cpuUsageText; + + const unsigned int tableSize = 1 << 7; + float level = 0.0f; + + juce::AudioSampleBuffer sineTable; + juce::OwnedArray oscillators; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainContentComponent) +}; diff --git a/Examples/WavetableSynthTutorial/Source/WavetableSynthTutorial_04.h b/Examples/WavetableSynthTutorial/Source/WavetableSynthTutorial_04.h new file mode 100644 index 0000000..9a7b12c --- /dev/null +++ b/Examples/WavetableSynthTutorial/Source/WavetableSynthTutorial_04.h @@ -0,0 +1,208 @@ +/* + ============================================================================== + + This file is part of the JUCE tutorials. + Copyright (c) 2020 - Raw Material Software Limited + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, + WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR + PURPOSE, ARE DISCLAIMED. + + ============================================================================== +*/ + +/******************************************************************************* + The block below describes the properties of this PIP. A PIP is a short snippet + of code that can be read by the Projucer and used to generate a JUCE project. + + BEGIN_JUCE_PIP_METADATA + + name: WavetableSynthTutorial + version: 4.0.0 + vendor: JUCE + website: http://juce.com + description: Wavetable synthesiser. + + dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats, + juce_audio_processors, juce_audio_utils, juce_core, + juce_data_structures, juce_events, juce_graphics, + juce_gui_basics, juce_gui_extra + exporters: xcode_mac, vs2019, linux_make + + type: Component + mainClass: MainContentComponent + + useLocalCopy: 1 + + END_JUCE_PIP_METADATA + +*******************************************************************************/ + + +#pragma once + +//============================================================================== +class WavetableOscillator +{ +public: + WavetableOscillator (const juce::AudioSampleBuffer& wavetableToUse) + : wavetable (wavetableToUse), + tableSize (wavetable.getNumSamples() - 1) + { + jassert (wavetable.getNumChannels() == 1); + } + + void setFrequency (float frequency, float sampleRate) + { + auto tableSizeOverSampleRate = (float) tableSize / sampleRate; + tableDelta = frequency * tableSizeOverSampleRate; + } + + forcedinline float getNextSample() noexcept + { + auto index0 = (unsigned int) currentIndex; + auto index1 = index0 + 1; + + auto frac = currentIndex - (float) index0; + + auto* table = wavetable.getReadPointer (0); + auto value0 = table[index0]; + auto value1 = table[index1]; + + auto currentSample = value0 + frac * (value1 - value0); + + if ((currentIndex += tableDelta) > (float) tableSize) + currentIndex -= (float) tableSize; + + return currentSample; + } + +private: + const juce::AudioSampleBuffer& wavetable; + const int tableSize; + float currentIndex = 0.0f, tableDelta = 0.0f; +}; + +//============================================================================== +class MainContentComponent : public juce::AudioAppComponent, + public juce::Timer +{ +public: + MainContentComponent() + { + cpuUsageLabel.setText ("CPU Usage", juce::dontSendNotification); + cpuUsageText.setJustificationType (juce::Justification::right); + addAndMakeVisible (cpuUsageLabel); + addAndMakeVisible (cpuUsageText); + + createWavetable(); + + setSize (400, 200); + setAudioChannels (0, 2); // no inputs, two outputs + startTimer (50); + } + + ~MainContentComponent() override + { + shutdownAudio(); + } + + void resized() override + { + cpuUsageLabel.setBounds (10, 10, getWidth() - 20, 20); + cpuUsageText .setBounds (10, 10, getWidth() - 20, 20); + } + + void timerCallback() override + { + auto cpu = deviceManager.getCpuUsage() * 100; + cpuUsageText.setText (juce::String (cpu, 6) + " %", juce::dontSendNotification); + } + + void createWavetable() + { + sineTable.setSize (1, (int) tableSize + 1); + sineTable.clear(); + + auto* samples = sineTable.getWritePointer (0); + + int harmonics[] = { 1, 3, 5, 6, 7, 9, 13, 15 }; + float harmonicWeights[] = { 0.5f, 0.1f, 0.05f, 0.125f, 0.09f, 0.005f, 0.002f, 0.001f }; // [1] + + jassert (juce::numElementsInArray (harmonics) == juce::numElementsInArray (harmonicWeights)); + + for (auto harmonic = 0; harmonic < juce::numElementsInArray (harmonics); ++harmonic) + { + auto angleDelta = juce::MathConstants::twoPi / (double) (tableSize - 1) * harmonics[harmonic]; // [2] + auto currentAngle = 0.0; + + for (unsigned int i = 0; i < tableSize; ++i) + { + auto sample = std::sin (currentAngle); + samples[i] += (float) sample * harmonicWeights[harmonic]; // [3] + currentAngle += angleDelta; + } + } + + samples[tableSize] = samples[0]; + } + + void prepareToPlay (int, double sampleRate) override + { + auto numberOfOscillators = 10; + + for (auto i = 0; i < numberOfOscillators; ++i) + { + auto* oscillator = new WavetableOscillator (sineTable); + + auto midiNote = juce::Random::getSystemRandom().nextDouble() * 36.0 + 48.0; + auto frequency = 440.0 * pow (2.0, (midiNote - 69.0) / 12.0); + + oscillator->setFrequency ((float) frequency, (float) sampleRate); + oscillators.add (oscillator); + } + + level = 0.25f / (float) numberOfOscillators; + } + + void releaseResources() override {} + + void getNextAudioBlock (const juce::AudioSourceChannelInfo& bufferToFill) override + { + auto* leftBuffer = bufferToFill.buffer->getWritePointer (0, bufferToFill.startSample); + auto* rightBuffer = bufferToFill.buffer->getWritePointer (1, bufferToFill.startSample); + + bufferToFill.clearActiveBufferRegion(); + + for (auto oscillatorIndex = 0; oscillatorIndex < oscillators.size(); ++oscillatorIndex) + { + auto* oscillator = oscillators.getUnchecked (oscillatorIndex); + + for (auto sample = 0; sample < bufferToFill.numSamples; ++sample) + { + auto levelSample = oscillator->getNextSample() * level; + + leftBuffer[sample] += levelSample; + rightBuffer[sample] += levelSample; + } + } + } + +private: + juce::Label cpuUsageLabel; + juce::Label cpuUsageText; + + const unsigned int tableSize = 1 << 7; + float level = 0.0f; + + juce::AudioSampleBuffer sineTable; + juce::OwnedArray oscillators; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainContentComponent) +}; diff --git a/Examples/WavetableSynthTutorial/WavetableSynthTutorial.jucer b/Examples/WavetableSynthTutorial/WavetableSynthTutorial.jucer new file mode 100644 index 0000000..b950cc2 --- /dev/null +++ b/Examples/WavetableSynthTutorial/WavetableSynthTutorial.jucer @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/JuceLibraryCode/JuceHeader.h b/JuceLibraryCode/JuceHeader.h index 9eccfa7..9320c75 100644 --- a/JuceLibraryCode/JuceHeader.h +++ b/JuceLibraryCode/JuceHeader.h @@ -43,7 +43,7 @@ namespace ProjectInfo { const char* const projectName = "NeuralSynth"; const char* const companyName = "Samedi Dimanche"; - const char* const versionString = "0.0.1"; - const int versionNumber = 0x1; + const char* const versionString = "1.0.0"; + const int versionNumber = 0x10000; } #endif diff --git a/JuceLibraryCode/JucePluginDefines.h b/JuceLibraryCode/JucePluginDefines.h index 6c0fa21..83b6b02 100644 --- a/JuceLibraryCode/JucePluginDefines.h +++ b/JuceLibraryCode/JucePluginDefines.h @@ -17,7 +17,7 @@ #define JucePlugin_Build_VST3 1 #endif #ifndef JucePlugin_Build_AU - #define JucePlugin_Build_AU 1 + #define JucePlugin_Build_AU 0 #endif #ifndef JucePlugin_Build_AUv3 #define JucePlugin_Build_AUv3 0 @@ -26,7 +26,7 @@ #define JucePlugin_Build_AAX 0 #endif #ifndef JucePlugin_Build_Standalone - #define JucePlugin_Build_Standalone 1 + #define JucePlugin_Build_Standalone 0 #endif #ifndef JucePlugin_Build_Unity #define JucePlugin_Build_Unity 0 @@ -77,13 +77,13 @@ #define JucePlugin_EditorRequiresKeyboardFocus 0 #endif #ifndef JucePlugin_Version - #define JucePlugin_Version 0.0.1 + #define JucePlugin_Version 1.0.0 #endif #ifndef JucePlugin_VersionCode - #define JucePlugin_VersionCode 0x1 + #define JucePlugin_VersionCode 0x10000 #endif #ifndef JucePlugin_VersionString - #define JucePlugin_VersionString "0.0.1" + #define JucePlugin_VersionString "1.0.0" #endif #ifndef JucePlugin_VSTUniqueID #define JucePlugin_VSTUniqueID JucePlugin_PluginCode diff --git a/NeuralSynth.jucer b/NeuralSynth.jucer index f0cdfd3..f5d28d9 100644 --- a/NeuralSynth.jucer +++ b/NeuralSynth.jucer @@ -4,7 +4,8 @@ addUsingNamespaceToJuceHeader="0" jucerFormatVersion="1" companyWebsite="www.samedidimanche.com" bundleIdentifier="com.samedidimanche.NeuralSynth" pluginManufacturer="Samedi Dimanche" companyName="Samedi Dimanche" pluginCharacteristicsValue="pluginIsSynth,pluginWantsMidiIn" - pluginVSTNumMidiInputs="1" pluginChannelConfigs="{0, 2}" version="0.0.1"> + pluginVSTNumMidiInputs="1" pluginChannelConfigs="{0, 2}" version="1.0.0" + pluginFormats="buildVST3"> @@ -70,5 +71,26 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index 664eddd..b14276c 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -8,12 +8,21 @@ NeuralSynthAudioProcessorEditor::NeuralSynthAudioProcessorEditor (NeuralSynthAudioProcessor& p) : AudioProcessorEditor (&p), audioProcessor (p), - mainScopeComponent(audioProcessor.getAudioBufferQueue()) + mainScopeComponent(audioProcessor.getAudioBufferQueue()), + keyboardComponent(keyboardState, juce::MidiKeyboardComponent::horizontalKeyboard) { auto& tree = audioProcessor.parameters; addAndMakeVisible(mainScopeComponent); + keyboardState.addListener(this); + keyboardComponent.setMidiChannel(1); + keyboardComponent.setScrollButtonsVisible(true); + keyboardComponent.setKeyWidth(36.0f); + keyboardComponent.setAvailableRange(36, 96); + keyboardComponent.setVelocity(1.0f, true); + addAndMakeVisible(keyboardComponent); + presetMenuButton.setButtonText("Preset"); presetMenuButton.onClick = [this] { showPresetMenu(); }; addAndMakeVisible(presetMenuButton); @@ -185,6 +194,7 @@ NeuralSynthAudioProcessorEditor::NeuralSynthAudioProcessorEditor (NeuralSynthAud NeuralSynthAudioProcessorEditor::~NeuralSynthAudioProcessorEditor() { stopTimer(); + keyboardState.removeListener(this); } //============================================================================== @@ -199,7 +209,7 @@ void NeuralSynthAudioProcessorEditor::resized() auto outer = getLocalBounds().reduced(16); // --- carve out sidebar for MASTER (right side) -------------------------- - const int sidebarWidth = 160; // tweak if you want it wider/narrower + const int sidebarWidth = 100; // tweak if you want it wider/narrower auto gridArea = outer; auto sidebar = gridArea.removeFromRight(sidebarWidth).reduced(8); @@ -217,17 +227,18 @@ void NeuralSynthAudioProcessorEditor::resized() juce::Grid grid; grid.templateRows = { - juce::Grid::TrackInfo(juce::Grid::Fr(22)), // scope band - juce::Grid::TrackInfo(juce::Grid::Fr(39)), // row 1 - juce::Grid::TrackInfo(juce::Grid::Fr(78)) // row 2 (wider to absorb layer panel) + juce::Grid::TrackInfo(juce::Grid::Fr(10)), // scope band + juce::Grid::TrackInfo(juce::Grid::Fr(35)), // row 1 + juce::Grid::TrackInfo(juce::Grid::Fr(35)), // row 2 + juce::Grid::TrackInfo(juce::Grid::Fr(20)) // keyboard }; grid.templateColumns = { - juce::Grid::TrackInfo(juce::Grid::Fr(1)), - juce::Grid::TrackInfo(juce::Grid::Fr(1)), - juce::Grid::TrackInfo(juce::Grid::Fr(1)), - juce::Grid::TrackInfo(juce::Grid::Fr(1)), - juce::Grid::TrackInfo(juce::Grid::Fr(1)) + juce::Grid::TrackInfo(juce::Grid::Fr(20)), + juce::Grid::TrackInfo(juce::Grid::Fr(20)), + juce::Grid::TrackInfo(juce::Grid::Fr(20)), + juce::Grid::TrackInfo(juce::Grid::Fr(20)), + juce::Grid::TrackInfo(juce::Grid::Fr(20)) }; grid.rowGap = juce::Grid::Px(0); @@ -236,7 +247,7 @@ void NeuralSynthAudioProcessorEditor::resized() grid.items.clear(); // Row 1 (scope row) - grid.items.add(juce::GridItem(mainScopeComponent).withArea(1, 1, 1, 5)); + grid.items.add(juce::GridItem(mainScopeComponent).withArea({}, juce::GridItem::Span(4))); // Put preset button at the top-right cell of the scope row grid.items.add(juce::GridItem(presetMenuButton) .withArea(1, 5) @@ -244,22 +255,39 @@ void NeuralSynthAudioProcessorEditor::resized() .withAlignSelf(juce::GridItem::AlignSelf::start)); // Row 2 (top row of panels): Amp Env, Chorus, Delay, Reverb, EQ - grid.items.add(juce::GridItem(*adsrComponent ).withArea(2, 1)); - grid.items.add(juce::GridItem(*chorusComponent ).withArea(2, 2)); - grid.items.add(juce::GridItem(*delayComponent ).withArea(2, 3)); - grid.items.add(juce::GridItem(*reverbComponent ).withArea(2, 4)); - grid.items.add(juce::GridItem(*eqComponent ).withArea(2, 5)); + grid.items.add(juce::GridItem(*adsrComponent )); + grid.items.add(juce::GridItem(*chorusComponent )); + grid.items.add(juce::GridItem(*delayComponent )); + grid.items.add(juce::GridItem(*reverbComponent )); + grid.items.add(juce::GridItem(*eqComponent )); // Row 3 (bottom row of panels): Flanger, Distortion, Filter, Filter Env, Wavetable - grid.items.add(juce::GridItem(*flangerComponent ).withArea(3, 1)); - grid.items.add(juce::GridItem(*distortionComponent).withArea(3, 2)); - grid.items.add(juce::GridItem(*filterComponent ).withArea(3, 3)); - grid.items.add(juce::GridItem(*filterEnvComponent ).withArea(3, 4)); - grid.items.add(juce::GridItem(*wtComponent ).withArea(3, 5)); + grid.items.add(juce::GridItem(*flangerComponent )); + grid.items.add(juce::GridItem(*distortionComponent)); + grid.items.add(juce::GridItem(*filterComponent )); + grid.items.add(juce::GridItem(*filterEnvComponent )); + grid.items.add(juce::GridItem(*wtComponent )); + + // Row 4: MIDI keyboard spans entire width + grid.items.add(juce::GridItem(keyboardComponent).withArea({}, juce::GridItem::Span(5))); grid.performLayout(gridArea); } +void NeuralSynthAudioProcessorEditor::handleNoteOn(juce::MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity) +{ + auto message = juce::MidiMessage::noteOn(midiChannel, midiNoteNumber, velocity); + message.setTimeStamp(juce::Time::getMillisecondCounterHiRes() * 0.001); + audioProcessor.midiMessageCollector.addMessageToQueue(message); +} + +void NeuralSynthAudioProcessorEditor::handleNoteOff(juce::MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity) +{ + auto message = juce::MidiMessage::noteOff(midiChannel, midiNoteNumber, velocity); + message.setTimeStamp(juce::Time::getMillisecondCounterHiRes() * 0.001); + audioProcessor.midiMessageCollector.addMessageToQueue(message); +} + void NeuralSynthAudioProcessorEditor::timerCallback() { const int current = audioProcessor.getCurrentPresetIndex(); diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index 8ec6691..07043d5 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -368,7 +368,8 @@ public: //============================== Editor ======================================= class NeuralSynthAudioProcessorEditor : public juce::AudioProcessorEditor, - private juce::Timer + private juce::Timer, + private juce::MidiKeyboardStateListener { public: NeuralSynthAudioProcessorEditor (NeuralSynthAudioProcessor&); @@ -377,6 +378,8 @@ public: void paint (juce::Graphics&) override; void resized() override; void timerCallback() override; + void handleNoteOn(juce::MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity) override; + void handleNoteOff(juce::MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity) override; private: NeuralSynthAudioProcessor& audioProcessor; @@ -408,6 +411,8 @@ private: std::unique_ptr gainAttachment; ScopeComponent mainScopeComponent; + juce::MidiKeyboardState keyboardState; + juce::MidiKeyboardComponent keyboardComponent; juce::ComboBox layerSelector; bool controllingLayerB { false }; }; diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index f051d70..4aa424d 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -210,14 +210,14 @@ void NeuralSynthAudioProcessor::prepareToPlay (double sampleRate, int samplesPer void NeuralSynthAudioProcessor::releaseResources() {} -bool NeuralSynthAudioProcessor::isBusesLayoutSupported (const BusesLayout& layouts) const +/*bool NeuralSynthAudioProcessor::isBusesLayoutSupported (const BusesLayout& layouts) const { if (layouts.getMainOutputChannelSet() != juce::AudioChannelSet::mono() && layouts.getMainOutputChannelSet() != juce::AudioChannelSet::stereo()) return false; return true; -} + }*/ void NeuralSynthAudioProcessor::processBlock(juce::AudioSampleBuffer& buffer, juce::MidiBuffer& midiMessages) { diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..79b3986 --- /dev/null +++ b/build.sh @@ -0,0 +1,4 @@ +pushd Builds/LinuxMakefile/ +make clean +make +popd