Fixes to the UI

This commit is contained in:
Tim
2025-10-26 00:49:50 +01:00
parent 0785f6fedd
commit c5105693a2
76 changed files with 2280 additions and 32 deletions

Binary file not shown.

View File

@@ -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 <juce_audio_basics/juce_audio_basics.h>
#include <juce_audio_devices/juce_audio_devices.h>
#include <juce_audio_formats/juce_audio_formats.h>
#include <juce_audio_processors/juce_audio_processors.h>
#include <juce_audio_utils/juce_audio_utils.h>
#include <juce_core/juce_core.h>
#include <juce_data_structures/juce_data_structures.h>
#include <juce_events/juce_events.h>
#include <juce_graphics/juce_graphics.h>
#include <juce_gui_basics/juce_gui_basics.h>
#include <juce_gui_extra/juce_gui_extra.h>
#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

View File

@@ -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).

View File

@@ -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 <juce_audio_basics/juce_audio_basics.cpp>

View File

@@ -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 <juce_audio_basics/juce_audio_basics.mm>

View File

@@ -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 <juce_audio_devices/juce_audio_devices.cpp>

View File

@@ -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 <juce_audio_devices/juce_audio_devices.mm>

View File

@@ -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 <juce_audio_formats/juce_audio_formats.cpp>

View File

@@ -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 <juce_audio_formats/juce_audio_formats.mm>

View File

@@ -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 <juce_audio_processors/juce_audio_processors.cpp>

View File

@@ -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 <juce_audio_processors/juce_audio_processors.mm>

View File

@@ -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 <juce_audio_processors/juce_audio_processors_ara.cpp>

View File

@@ -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 <juce_audio_processors/juce_audio_processors_lv2_libs.cpp>

View File

@@ -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 <juce_audio_utils/juce_audio_utils.cpp>

View File

@@ -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 <juce_audio_utils/juce_audio_utils.mm>

View File

@@ -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 <juce_core/juce_core.cpp>

View File

@@ -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 <juce_core/juce_core.mm>

View File

@@ -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 <juce_core/juce_core_CompilationTime.cpp>

View File

@@ -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 <juce_data_structures/juce_data_structures.cpp>

View File

@@ -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 <juce_data_structures/juce_data_structures.mm>

View File

@@ -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 <juce_events/juce_events.cpp>

View File

@@ -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 <juce_events/juce_events.mm>

View File

@@ -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 <juce_graphics/juce_graphics.cpp>

View File

@@ -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 <juce_graphics/juce_graphics.mm>

View File

@@ -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 <juce_graphics/juce_graphics_Harfbuzz.cpp>

View File

@@ -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 <juce_graphics/juce_graphics_Sheenbidi.c>

View File

@@ -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 <juce_gui_basics/juce_gui_basics.cpp>

View File

@@ -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 <juce_gui_basics/juce_gui_basics.mm>

View File

@@ -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 <juce_gui_extra/juce_gui_extra.cpp>

View File

@@ -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 <juce_gui_extra/juce_gui_extra.mm>

View File

@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<JUCERPROJECT name="MPEIntroductionTutorial" companyName="JUCE" version="1.0.0"
userNotes="Synthesiser using MPE specifications." companyWebsite="http://juce.com"
projectType="guiapp" useAppConfig="0" addUsingNamespaceToJuceHeader="1"
id="JGbUDb" jucerFormatVersion="1">
<MAINGROUP id="V9kDzX" name="MPEIntroductionTutorial">
<GROUP id="{DD17E4C5-2693-D5AD-A257-4695D52ED04A}" name="Source">
<FILE id="UcuJ7R" name="Main.cpp" compile="1" resource="0" file="Source/Main.cpp"/>
<FILE id="hNsrsX" name="MPEIntroductionTutorial.h" compile="0" resource="0"
file="Source/MPEIntroductionTutorial.h"/>
</GROUP>
</MAINGROUP>
<MODULES>
<MODULE id="juce_audio_basics" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_audio_devices" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_audio_formats" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_audio_processors" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_audio_utils" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_core" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_data_structures" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_events" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_graphics" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_gui_basics" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_gui_extra" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
</MODULES>
<EXPORTFORMATS>
<LINUX_MAKE targetFolder="Builds/LinuxMakefile">
<CONFIGURATIONS>
<CONFIGURATION name="Debug" isDebug="1" optimisation="1" targetName="MPEIntroductionTutorial"/>
<CONFIGURATION name="Release" isDebug="0" optimisation="3" targetName="MPEIntroductionTutorial"/>
</CONFIGURATIONS>
<MODULEPATHS>
<MODULEPATH id="juce_audio_basics" path=""/>
<MODULEPATH id="juce_audio_devices" path=""/>
<MODULEPATH id="juce_audio_formats" path=""/>
<MODULEPATH id="juce_audio_processors" path=""/>
<MODULEPATH id="juce_audio_utils" path=""/>
<MODULEPATH id="juce_core" path=""/>
<MODULEPATH id="juce_data_structures" path=""/>
<MODULEPATH id="juce_events" path=""/>
<MODULEPATH id="juce_graphics" path=""/>
<MODULEPATH id="juce_gui_basics" path=""/>
<MODULEPATH id="juce_gui_extra" path=""/>
</MODULEPATHS>
</LINUX_MAKE>
</EXPORTFORMATS>
<JUCEOPTIONS/>
</JUCERPROJECT>

View File

@@ -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<float> 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<float> 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<float> getSquareAroundCentre (float radius) const noexcept
{
return juce::Rectangle<float> (radius * 2.0f, radius * 2.0f).withCentre (centre);
}
juce::Rectangle<float> translateToLocalBounds (juce::Rectangle<float> r) const noexcept
{
return r - getPosition().toFloat();
}
juce::Rectangle<float> getTextRectangle() const noexcept
{
return juce::Rectangle<float> (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 &note;
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<float> 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<NoteComponent> noteComponents;
juce::CriticalSection lock;
juce::Array<juce::MPENote> 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<double>::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<float>& 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<double>::pi * cyclesPerSample;
phase = std::fmod (phase + phaseDelta, 2.0 * juce::MathConstants<double>::pi);
return nextSample;
}
//==============================================================================
juce::SmoothedValue<double> 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<float> 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)
};

View File

@@ -0,0 +1,96 @@
/*
==============================================================================
This file contains the startup code for a PIP.
==============================================================================
*/
#include <JuceHeader.h>
#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<MainComponent>(), *this));
}
void shutdown() override { mainWindow = nullptr; }
private:
class MainWindow : public juce::DocumentWindow
{
public:
MainWindow (const juce::String& name, std::unique_ptr<juce::Component> 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<Component> 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<Component> 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> mainWindow;
};
//==============================================================================
START_JUCE_APPLICATION (Application)

Binary file not shown.

View File

@@ -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 <juce_audio_basics/juce_audio_basics.h>
#include <juce_audio_devices/juce_audio_devices.h>
#include <juce_audio_formats/juce_audio_formats.h>
#include <juce_audio_processors/juce_audio_processors.h>
#include <juce_audio_utils/juce_audio_utils.h>
#include <juce_core/juce_core.h>
#include <juce_data_structures/juce_data_structures.h>
#include <juce_events/juce_events.h>
#include <juce_graphics/juce_graphics.h>
#include <juce_gui_basics/juce_gui_basics.h>
#include <juce_gui_extra/juce_gui_extra.h>
#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

View File

@@ -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).

View File

@@ -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 <juce_audio_basics/juce_audio_basics.cpp>

View File

@@ -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 <juce_audio_basics/juce_audio_basics.mm>

View File

@@ -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 <juce_audio_devices/juce_audio_devices.cpp>

View File

@@ -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 <juce_audio_devices/juce_audio_devices.mm>

View File

@@ -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 <juce_audio_formats/juce_audio_formats.cpp>

View File

@@ -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 <juce_audio_formats/juce_audio_formats.mm>

View File

@@ -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 <juce_audio_processors/juce_audio_processors.cpp>

View File

@@ -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 <juce_audio_processors/juce_audio_processors.mm>

View File

@@ -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 <juce_audio_processors/juce_audio_processors_ara.cpp>

View File

@@ -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 <juce_audio_processors/juce_audio_processors_lv2_libs.cpp>

View File

@@ -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 <juce_audio_utils/juce_audio_utils.cpp>

View File

@@ -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 <juce_audio_utils/juce_audio_utils.mm>

View File

@@ -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 <juce_core/juce_core.cpp>

View File

@@ -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 <juce_core/juce_core.mm>

View File

@@ -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 <juce_core/juce_core_CompilationTime.cpp>

View File

@@ -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 <juce_data_structures/juce_data_structures.cpp>

View File

@@ -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 <juce_data_structures/juce_data_structures.mm>

View File

@@ -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 <juce_events/juce_events.cpp>

View File

@@ -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 <juce_events/juce_events.mm>

View File

@@ -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 <juce_graphics/juce_graphics.cpp>

View File

@@ -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 <juce_graphics/juce_graphics.mm>

View File

@@ -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 <juce_graphics/juce_graphics_Harfbuzz.cpp>

View File

@@ -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 <juce_graphics/juce_graphics_Sheenbidi.c>

View File

@@ -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 <juce_gui_basics/juce_gui_basics.cpp>

View File

@@ -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 <juce_gui_basics/juce_gui_basics.mm>

View File

@@ -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 <juce_gui_extra/juce_gui_extra.cpp>

View File

@@ -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 <juce_gui_extra/juce_gui_extra.mm>

View File

@@ -0,0 +1,96 @@
/*
==============================================================================
This file contains the startup code for a PIP.
==============================================================================
*/
#include <JuceHeader.h>
#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<MainContentComponent>(), *this));
}
void shutdown() override { mainWindow = nullptr; }
private:
class MainWindow : public juce::DocumentWindow
{
public:
MainWindow (const juce::String& name, std::unique_ptr<juce::Component> 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<Component> 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<Component> 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> mainWindow;
};
//==============================================================================
START_JUCE_APPLICATION (Application)

View File

@@ -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<float>::twoPi;
}
forcedinline void updateAngle() noexcept
{
currentAngle += angleDelta;
if (currentAngle >= juce::MathConstants<float>::twoPi)
currentAngle -= juce::MathConstants<float>::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<SineOscillator> oscillators;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainContentComponent)
};

View File

@@ -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<double>::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<WavetableOscillator> oscillators;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainContentComponent)
};

View File

@@ -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<double>::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<WavetableOscillator> oscillators;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainContentComponent)
};

View File

@@ -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<double>::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<WavetableOscillator> oscillators;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainContentComponent)
};

View File

@@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<JUCERPROJECT name="WavetableSynthTutorial" companyName="JUCE" version="1.0.0"
userNotes="Wavetable synthesiser." companyWebsite="http://juce.com"
projectType="guiapp" useAppConfig="0" addUsingNamespaceToJuceHeader="1"
id="dBFoEo" jucerFormatVersion="1">
<MAINGROUP id="auHq7A" name="WavetableSynthTutorial">
<GROUP id="{45834796-7C04-D188-9F98-9AE7211B6CF5}" name="Source">
<FILE id="iPmvtR" name="Main.cpp" compile="1" resource="0" file="Source/Main.cpp"/>
<FILE id="EKiEzl" name="WavetableSynthTutorial_01.h" compile="0" resource="0"
file="Source/WavetableSynthTutorial_01.h"/>
</GROUP>
</MAINGROUP>
<MODULES>
<MODULE id="juce_audio_basics" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_audio_devices" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_audio_formats" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_audio_processors" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_audio_utils" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_core" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_data_structures" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_events" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_graphics" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_gui_basics" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_gui_extra" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
</MODULES>
<EXPORTFORMATS>
<XCODE_MAC targetFolder="Builds/MacOSX">
<CONFIGURATIONS>
<CONFIGURATION name="Debug" isDebug="1" optimisation="1" targetName="WavetableSynthTutorial"/>
<CONFIGURATION name="Release" isDebug="0" optimisation="3" targetName="WavetableSynthTutorial"/>
</CONFIGURATIONS>
<MODULEPATHS>
<MODULEPATH id="juce_audio_basics" path=""/>
<MODULEPATH id="juce_audio_devices" path=""/>
<MODULEPATH id="juce_audio_formats" path=""/>
<MODULEPATH id="juce_audio_processors" path=""/>
<MODULEPATH id="juce_audio_utils" path=""/>
<MODULEPATH id="juce_core" path=""/>
<MODULEPATH id="juce_data_structures" path=""/>
<MODULEPATH id="juce_events" path=""/>
<MODULEPATH id="juce_graphics" path=""/>
<MODULEPATH id="juce_gui_basics" path=""/>
<MODULEPATH id="juce_gui_extra" path=""/>
</MODULEPATHS>
</XCODE_MAC>
<VS2019 targetFolder="Builds/VisualStudio2019">
<CONFIGURATIONS>
<CONFIGURATION name="Debug" isDebug="1" optimisation="1" targetName="WavetableSynthTutorial"/>
<CONFIGURATION name="Release" isDebug="0" optimisation="3" targetName="WavetableSynthTutorial"/>
</CONFIGURATIONS>
<MODULEPATHS>
<MODULEPATH id="juce_audio_basics" path=""/>
<MODULEPATH id="juce_audio_devices" path=""/>
<MODULEPATH id="juce_audio_formats" path=""/>
<MODULEPATH id="juce_audio_processors" path=""/>
<MODULEPATH id="juce_audio_utils" path=""/>
<MODULEPATH id="juce_core" path=""/>
<MODULEPATH id="juce_data_structures" path=""/>
<MODULEPATH id="juce_events" path=""/>
<MODULEPATH id="juce_graphics" path=""/>
<MODULEPATH id="juce_gui_basics" path=""/>
<MODULEPATH id="juce_gui_extra" path=""/>
</MODULEPATHS>
</VS2019>
<LINUX_MAKE targetFolder="Builds/LinuxMakefile">
<CONFIGURATIONS>
<CONFIGURATION name="Debug" isDebug="1" optimisation="1" targetName="WavetableSynthTutorial"/>
<CONFIGURATION name="Release" isDebug="0" optimisation="3" targetName="WavetableSynthTutorial"/>
</CONFIGURATIONS>
<MODULEPATHS>
<MODULEPATH id="juce_audio_basics" path=""/>
<MODULEPATH id="juce_audio_devices" path=""/>
<MODULEPATH id="juce_audio_formats" path=""/>
<MODULEPATH id="juce_audio_processors" path=""/>
<MODULEPATH id="juce_audio_utils" path=""/>
<MODULEPATH id="juce_core" path=""/>
<MODULEPATH id="juce_data_structures" path=""/>
<MODULEPATH id="juce_events" path=""/>
<MODULEPATH id="juce_graphics" path=""/>
<MODULEPATH id="juce_gui_basics" path=""/>
<MODULEPATH id="juce_gui_extra" path=""/>
</MODULEPATHS>
</LINUX_MAKE>
</EXPORTFORMATS>
<JUCEOPTIONS/>
</JUCERPROJECT>

View File

@@ -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

View File

@@ -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

View File

@@ -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">
<MAINGROUP id="UQstsW" name="NeuralSynth">
<GROUP id="{D5B48DA9-9A47-914A-8C72-EE5E8DD868A3}" name="Source">
<FILE id="Mkx0uo" name="BlepOsc.h" compile="0" resource="0" file="Source/BlepOsc.h"/>
@@ -70,5 +71,26 @@
<MODULEPATH id="juce_gui_extra" path="../../../../../JUCE/modules"/>
</MODULEPATHS>
</VS2022>
<LINUX_MAKE targetFolder="Builds/LinuxMakefile">
<CONFIGURATIONS>
<CONFIGURATION isDebug="1" name="Debug"/>
<CONFIGURATION isDebug="0" name="Release"/>
</CONFIGURATIONS>
<MODULEPATHS>
<MODULEPATH id="juce_audio_basics" path="../../juce"/>
<MODULEPATH id="juce_audio_devices" path="../../juce"/>
<MODULEPATH id="juce_audio_formats" path="../../juce"/>
<MODULEPATH id="juce_audio_plugin_client" path="../../juce"/>
<MODULEPATH id="juce_audio_processors" path="../../juce"/>
<MODULEPATH id="juce_audio_utils" path="../../juce"/>
<MODULEPATH id="juce_core" path="../../juce"/>
<MODULEPATH id="juce_data_structures" path="../../juce"/>
<MODULEPATH id="juce_dsp" path="../../juce"/>
<MODULEPATH id="juce_events" path="../../juce"/>
<MODULEPATH id="juce_graphics" path="../../juce"/>
<MODULEPATH id="juce_gui_basics" path="../../juce"/>
<MODULEPATH id="juce_gui_extra" path="../../juce"/>
</MODULEPATHS>
</LINUX_MAKE>
</EXPORTFORMATS>
</JUCERPROJECT>

View File

@@ -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();

View File

@@ -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<juce::AudioProcessorValueTreeState::SliderAttachment> gainAttachment;
ScopeComponent<float> mainScopeComponent;
juce::MidiKeyboardState keyboardState;
juce::MidiKeyboardComponent keyboardComponent;
juce::ComboBox layerSelector;
bool controllingLayerB { false };
};

View File

@@ -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)
{

4
build.sh Executable file
View File

@@ -0,0 +1,4 @@
pushd Builds/LinuxMakefile/
make clean
make
popd