initial scaffold: JUCE VST3/AU plugin (Path 2)
Cross-platform DAW plugin that uploads audio to registry.houseofmixtape.com and imports stems back into the host DAW. Skeleton — does not build yet. To build: 1. Clone JUCE next to this repo: git clone https://github.com/juce-framework/JUCE 2. cmake -B build -DJUCE_DIR=../JUCE 3. cmake --build build --target HouseOfMixtape_VST3 Files: - CMakeLists.txt — juce_add_plugin VST3+AU+Standalone - Source/PluginProcessor.{h,cpp} — passthrough AudioProcessor - Source/PluginEditor.{h,cpp} — drag-drop + Upload btn + progress (Paper&Ink dark) - Source/HomApiClient.{h,cpp} — HTTP skeleton for /api/jobs/* (real wiring deferred) - README.md — full build instructions + roadmap This commit is the scaffold checkpoint. Real HTTP wiring + DAW file-import API integration land in subsequent commits.
This commit is contained in:
commit
f466c5cdad
|
|
@ -0,0 +1,61 @@
|
|||
cmake_minimum_required(VERSION 3.22)
|
||||
|
||||
project(HouseOfMixtape VERSION 0.1.0 LANGUAGES C CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
# JUCE is expected as a sibling directory by default. Override with
|
||||
# -DJUCE_DIR=/path/to/JUCE if you keep it elsewhere.
|
||||
if(NOT DEFINED JUCE_DIR)
|
||||
set(JUCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../JUCE")
|
||||
endif()
|
||||
|
||||
if(NOT EXISTS "${JUCE_DIR}/CMakeLists.txt")
|
||||
message(FATAL_ERROR
|
||||
"JUCE not found at ${JUCE_DIR}. "
|
||||
"Clone https://github.com/juce-framework/JUCE next to this dir, "
|
||||
"or pass -DJUCE_DIR=/path/to/JUCE."
|
||||
)
|
||||
endif()
|
||||
|
||||
add_subdirectory(${JUCE_DIR} JUCE)
|
||||
|
||||
juce_add_plugin(HouseOfMixtape
|
||||
COMPANY_NAME "House of Mixtape"
|
||||
BUNDLE_ID "com.houseofmixtape.daw"
|
||||
PLUGIN_MANUFACTURER_CODE HoMx
|
||||
PLUGIN_CODE HoM1
|
||||
FORMATS VST3 AU Standalone
|
||||
PRODUCT_NAME "House of Mixtape"
|
||||
NEEDS_MIDI_INPUT FALSE
|
||||
NEEDS_MIDI_OUTPUT FALSE
|
||||
IS_SYNTH FALSE
|
||||
EDITOR_WANTS_KEYBOARD_FOCUS TRUE
|
||||
COPY_PLUGIN_AFTER_BUILD TRUE
|
||||
VST3_CATEGORIES "Fx" "Tools"
|
||||
AU_MAIN_TYPE "kAudioUnitType_Effect"
|
||||
)
|
||||
|
||||
target_sources(HouseOfMixtape PRIVATE
|
||||
Source/PluginProcessor.cpp
|
||||
Source/PluginEditor.cpp
|
||||
Source/HomApiClient.cpp
|
||||
)
|
||||
|
||||
target_compile_definitions(HouseOfMixtape PRIVATE
|
||||
JUCE_WEB_BROWSER=0
|
||||
JUCE_USE_CURL=1
|
||||
JUCE_VST3_CAN_REPLACE_VST2=0
|
||||
)
|
||||
|
||||
target_link_libraries(HouseOfMixtape PRIVATE
|
||||
juce::juce_audio_utils
|
||||
juce::juce_dsp
|
||||
juce::juce_gui_extra
|
||||
juce::juce_audio_formats
|
||||
juce::juce_audio_devices
|
||||
juce::juce_recommended_config_flags
|
||||
juce::juce_recommended_lto_flags
|
||||
juce::juce_recommended_warning_flags
|
||||
)
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
# House of Mixtape — DAW Plugin (Path 2)
|
||||
|
||||
Cross-platform VST3/AU plugin that lets the user upload audio from inside
|
||||
their DAW and receive Phoenix-extracted stems + MIDI directly into the
|
||||
arrangement. Targets Win/Mac/Linux. AAX (Pro Tools) is a v2 ambition.
|
||||
|
||||
## Status: scaffolding (2026-05-03)
|
||||
|
||||
This directory holds the JUCE project skeleton + build instructions. The
|
||||
actual plugin is **not yet compiled** — that's a separate sprint that
|
||||
needs:
|
||||
|
||||
1. JUCE installed (clone https://github.com/juce-framework/JUCE)
|
||||
2. CMake 3.22+
|
||||
3. Per-OS toolchain (MSVC on Win, Xcode on macOS, gcc/clang on Linux)
|
||||
|
||||
The skeleton is enough to verify the build pipeline. Once a developer
|
||||
has JUCE + CMake set up locally, `cmake -B build && cmake --build build`
|
||||
should produce a working empty plugin that loads in Ableton/FL/Logic.
|
||||
|
||||
## Why JUCE (vs alternatives)
|
||||
|
||||
- **JUCE** : industry standard for VST3/AU plugins. Free for open-source,
|
||||
paid licence ($800/yr indie) for commercial. Cross-platform out of the
|
||||
box. Used by everyone (Pro-Q, Serum, Massive, ToneBoosters…).
|
||||
- iPlug2 : MIT-licenced alternative. Smaller community. Considered.
|
||||
- DPF : niche.
|
||||
- Faust : DSP-only, not for HTTP-talking plugins.
|
||||
|
||||
JUCE wins on docs + support + the fact that we'll integrate HTTP +
|
||||
file-import in C++, which is well-trod ground there.
|
||||
|
||||
## What the plugin does
|
||||
|
||||
```
|
||||
┌───────────── DAW (Ableton/FL/Logic/Reaper) ─────────────┐
|
||||
│ │
|
||||
│ ┌──────────────────────────────────┐ │
|
||||
│ │ House of Mixtape (VST3 plugin) │ │
|
||||
│ │ │ │
|
||||
│ │ 1. User drag-drops audio │ │
|
||||
│ │ 2. POST registry.../jobs/upload │ │
|
||||
│ │ 3. POST .../jobs/submit │ │
|
||||
│ │ 4. Poll /status (progress bar) │ │
|
||||
│ │ 5. GET /output → stems + MIDI │ │
|
||||
│ │ 6. Drop stems into DAW tracks │ │
|
||||
│ │ (via VST3 host import API) │ │
|
||||
│ └──────────────────────────────────┘ │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Files
|
||||
|
||||
| Path | Purpose |
|
||||
|---|---|
|
||||
| `CMakeLists.txt` | JUCE+CMake project. References JUCE submodule. |
|
||||
| `Source/PluginProcessor.{h,cpp}` | AudioProcessor — does no DSP yet, just hosts the editor. |
|
||||
| `Source/PluginEditor.{h,cpp}` | The plugin UI. Skeleton: Upload button + status text. |
|
||||
| `Source/HomApiClient.{h,cpp}` | HTTP client for `registry.houseofmixtape.com/api/jobs/*`. Skeleton stubs. |
|
||||
|
||||
## Build (when ready)
|
||||
|
||||
```bash
|
||||
# Clone JUCE next to this dir
|
||||
cd ..
|
||||
git clone --depth 1 https://github.com/juce-framework/JUCE.git
|
||||
cd daw-plugin
|
||||
|
||||
# Configure + build (Linux example)
|
||||
cmake -B build -DCMAKE_BUILD_TYPE=Release \
|
||||
-DJUCE_BUILD_TESTS=OFF \
|
||||
-DJUCE_DIR=$(pwd)/../JUCE
|
||||
cmake --build build --target HouseOfMixtape_VST3 -j
|
||||
|
||||
# Output:
|
||||
# build/HouseOfMixtape_artefacts/Release/VST3/HouseOfMixtape.vst3/
|
||||
```
|
||||
|
||||
## Roadmap
|
||||
|
||||
- [x] Scaffold (this commit)
|
||||
- [ ] HelloWorld build verified on at least one OS
|
||||
- [ ] HTTP client wired to `registry.houseofmixtape.com`
|
||||
- [ ] Bearer auth flow
|
||||
- [ ] Audio drag-drop in plugin window
|
||||
- [ ] Status polling + progress bar
|
||||
- [ ] Stems import to DAW tracks (VST3 host file-drop API)
|
||||
- [ ] AU build (macOS)
|
||||
- [ ] Code signing + notarization (macOS)
|
||||
- [ ] Win installer
|
||||
- [ ] Distribution (Gumroad? Free? Subscription?)
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
#include "HomApiClient.h"
|
||||
|
||||
HomApiClient::HomApiClient (juce::String registryBaseUrl)
|
||||
: baseUrl (std::move (registryBaseUrl))
|
||||
{
|
||||
}
|
||||
|
||||
void HomApiClient::submitJobAsync (const juce::File& audio,
|
||||
ProgressCallback onProgress,
|
||||
DoneCallback onDone,
|
||||
ErrorCallback onError)
|
||||
{
|
||||
juce::ignoreUnused (audio);
|
||||
|
||||
// Skeleton — fire a fake progress sweep then signal "not implemented".
|
||||
// Real implementation:
|
||||
// 1) POST baseUrl + "/jobs/upload" with multipart audio file
|
||||
// 2) POST baseUrl + "/jobs/submit" with the returned key
|
||||
// 3) Poll baseUrl + "/jobs/{id}/status" every ~3s
|
||||
// 4) GET baseUrl + "/jobs/{id}/output" → stems URLs
|
||||
// 5) Download each stem to a temp dir
|
||||
// 6) Trigger the host DAW to import them as new tracks
|
||||
// (FileDragAndDropHandling on the host's edit window)
|
||||
|
||||
juce::Thread::launch ([onProgress, onError]
|
||||
{
|
||||
for (double p : { 0.05, 0.20, 0.50, 0.80 })
|
||||
{
|
||||
juce::Thread::sleep (300);
|
||||
if (onProgress) onProgress (p);
|
||||
}
|
||||
if (onError)
|
||||
onError ("API client skeleton — wire HTTP in next sprint.");
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
#pragma once
|
||||
|
||||
#include <juce_core/juce_core.h>
|
||||
#include <juce_audio_basics/juce_audio_basics.h>
|
||||
#include <functional>
|
||||
|
||||
/**
|
||||
* House of Mixtape API client (Path 2 plugin → registry.houseofmixtape.com).
|
||||
* Skeleton — submit/poll/output endpoints are stubbed; the actual
|
||||
* HTTP/JUCE URL wiring lands in a follow-up commit.
|
||||
*/
|
||||
class HomApiClient
|
||||
{
|
||||
public:
|
||||
using ProgressCallback = std::function<void (double)>;
|
||||
using DoneCallback = std::function<void (juce::var)>;
|
||||
using ErrorCallback = std::function<void (const juce::String&)>;
|
||||
|
||||
explicit HomApiClient (juce::String registryBaseUrl);
|
||||
|
||||
// Async: upload + submit + poll until done|error.
|
||||
void submitJobAsync (const juce::File& audio,
|
||||
ProgressCallback onProgress,
|
||||
DoneCallback onDone,
|
||||
ErrorCallback onError);
|
||||
|
||||
void setBearerToken (const juce::String& token) { bearer = token; }
|
||||
|
||||
private:
|
||||
juce::String baseUrl;
|
||||
juce::String bearer;
|
||||
};
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
#include "PluginEditor.h"
|
||||
|
||||
HomAudioProcessorEditor::HomAudioProcessorEditor (HomAudioProcessor& p)
|
||||
: AudioProcessorEditor (&p), processor (p),
|
||||
progressBar (progressValue)
|
||||
{
|
||||
setSize (440, 240);
|
||||
setResizable (true, true);
|
||||
|
||||
addAndMakeVisible (uploadBtn);
|
||||
uploadBtn.onClick = [this] { onUploadClicked(); };
|
||||
|
||||
addAndMakeVisible (statusLbl);
|
||||
statusLbl.setText ("Drop an audio file or click the button.",
|
||||
juce::dontSendNotification);
|
||||
statusLbl.setJustificationType (juce::Justification::centred);
|
||||
|
||||
addAndMakeVisible (progressBar);
|
||||
progressBar.setVisible (false);
|
||||
}
|
||||
|
||||
void HomAudioProcessorEditor::paint (juce::Graphics& g)
|
||||
{
|
||||
// Paper&Ink dark
|
||||
g.fillAll (juce::Colour (0xff1A1715));
|
||||
g.setColour (juce::Colour (0xffD4A574));
|
||||
g.setFont (16.0f);
|
||||
g.drawFittedText ("House of Mixtape",
|
||||
getLocalBounds().removeFromTop (40),
|
||||
juce::Justification::centred, 1);
|
||||
}
|
||||
|
||||
void HomAudioProcessorEditor::resized()
|
||||
{
|
||||
auto r = getLocalBounds().reduced (16);
|
||||
r.removeFromTop (40); // brand header
|
||||
|
||||
statusLbl.setBounds (r.removeFromTop (32));
|
||||
r.removeFromTop (12);
|
||||
uploadBtn.setBounds (r.removeFromTop (44).reduced (40, 0));
|
||||
r.removeFromTop (16);
|
||||
progressBar.setBounds (r.removeFromTop (24));
|
||||
}
|
||||
|
||||
bool HomAudioProcessorEditor::isInterestedInFileDrag (const juce::StringArray& files)
|
||||
{
|
||||
for (auto& f : files)
|
||||
if (f.endsWithIgnoreCase (".wav") || f.endsWithIgnoreCase (".mp3")
|
||||
|| f.endsWithIgnoreCase (".flac") || f.endsWithIgnoreCase (".m4a"))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void HomAudioProcessorEditor::filesDropped (const juce::StringArray& files, int, int)
|
||||
{
|
||||
if (files.isEmpty()) return;
|
||||
setStatus ("Uploading " + juce::File (files[0]).getFileName() + "…");
|
||||
progressBar.setVisible (true);
|
||||
progressValue = 0.05;
|
||||
|
||||
// Hand off to the API client (skeleton — actual HTTP wiring lands in
|
||||
// a subsequent commit once Path 2 sprint resumes).
|
||||
processor.getApiClient().submitJobAsync (
|
||||
juce::File (files[0]),
|
||||
/* onProgress */ [this] (double p) { progressValue = p; },
|
||||
/* onDone */ [this] (juce::var output) {
|
||||
juce::ignoreUnused (output);
|
||||
progressBar.setVisible (false);
|
||||
setStatus ("Done — stems imported into the project.");
|
||||
},
|
||||
/* onError */ [this] (const juce::String& msg) {
|
||||
progressBar.setVisible (false);
|
||||
setStatus ("Error: " + msg);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void HomAudioProcessorEditor::onUploadClicked()
|
||||
{
|
||||
auto chooser = std::make_shared<juce::FileChooser> (
|
||||
"Choose an audio file",
|
||||
juce::File::getSpecialLocation (juce::File::userMusicDirectory),
|
||||
"*.wav;*.mp3;*.flac;*.m4a"
|
||||
);
|
||||
auto flags = juce::FileBrowserComponent::openMode | juce::FileBrowserComponent::canSelectFiles;
|
||||
chooser->launchAsync (flags, [this, chooser] (const juce::FileChooser& fc) {
|
||||
auto files = fc.getResults();
|
||||
if (files.isEmpty()) return;
|
||||
juce::StringArray paths;
|
||||
paths.add (files[0].getFullPathName());
|
||||
filesDropped (paths, 0, 0);
|
||||
});
|
||||
}
|
||||
|
||||
void HomAudioProcessorEditor::setStatus (const juce::String& msg)
|
||||
{
|
||||
statusLbl.setText (msg, juce::dontSendNotification);
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
#pragma once
|
||||
|
||||
#include <juce_audio_processors/juce_audio_processors.h>
|
||||
#include "PluginProcessor.h"
|
||||
|
||||
class HomAudioProcessorEditor : public juce::AudioProcessorEditor,
|
||||
public juce::FileDragAndDropTarget
|
||||
{
|
||||
public:
|
||||
explicit HomAudioProcessorEditor (HomAudioProcessor&);
|
||||
~HomAudioProcessorEditor() override = default;
|
||||
|
||||
void paint (juce::Graphics&) override;
|
||||
void resized() override;
|
||||
|
||||
// FileDragAndDropTarget
|
||||
bool isInterestedInFileDrag (const juce::StringArray& files) override;
|
||||
void filesDropped (const juce::StringArray& files, int x, int y) override;
|
||||
|
||||
private:
|
||||
HomAudioProcessor& processor;
|
||||
|
||||
juce::TextButton uploadBtn { "Drop / browse audio" };
|
||||
juce::Label statusLbl;
|
||||
juce::ProgressBar progressBar;
|
||||
double progressValue { 0.0 };
|
||||
|
||||
void onUploadClicked();
|
||||
void setStatus (const juce::String& msg);
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (HomAudioProcessorEditor)
|
||||
};
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
#include "PluginProcessor.h"
|
||||
#include "PluginEditor.h"
|
||||
|
||||
HomAudioProcessor::HomAudioProcessor()
|
||||
: AudioProcessor (BusesProperties()
|
||||
.withInput ("Input", juce::AudioChannelSet::stereo(), true)
|
||||
.withOutput ("Output", juce::AudioChannelSet::stereo(), true)),
|
||||
apiClient ("https://registry.houseofmixtape.com/api")
|
||||
{
|
||||
}
|
||||
|
||||
void HomAudioProcessor::prepareToPlay (double, int) {}
|
||||
void HomAudioProcessor::releaseResources() {}
|
||||
|
||||
bool HomAudioProcessor::isBusesLayoutSupported (const BusesLayout& layouts) const
|
||||
{
|
||||
// Mono or stereo only (passthrough — we don't process audio yet)
|
||||
if (layouts.getMainOutputChannelSet() != juce::AudioChannelSet::mono()
|
||||
&& layouts.getMainOutputChannelSet() != juce::AudioChannelSet::stereo())
|
||||
return false;
|
||||
return layouts.getMainInputChannelSet() == layouts.getMainOutputChannelSet();
|
||||
}
|
||||
|
||||
void HomAudioProcessor::processBlock (juce::AudioBuffer<float>& buffer, juce::MidiBuffer&)
|
||||
{
|
||||
// Passthrough — the plugin's value is in the GUI (upload + receive stems),
|
||||
// not in real-time DSP.
|
||||
juce::ignoreUnused (buffer);
|
||||
}
|
||||
|
||||
juce::AudioProcessorEditor* HomAudioProcessor::createEditor()
|
||||
{
|
||||
return new HomAudioProcessorEditor (*this);
|
||||
}
|
||||
|
||||
void HomAudioProcessor::getStateInformation (juce::MemoryBlock&) {}
|
||||
void HomAudioProcessor::setStateInformation (const void*, int) {}
|
||||
|
||||
// JUCE plugin entry point
|
||||
juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter()
|
||||
{
|
||||
return new HomAudioProcessor();
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
// House of Mixtape — DAW Plugin (Path 2)
|
||||
// Skeleton AudioProcessor. Does no DSP yet; lives only to host the editor.
|
||||
#pragma once
|
||||
|
||||
#include <juce_audio_processors/juce_audio_processors.h>
|
||||
#include "HomApiClient.h"
|
||||
|
||||
class HomAudioProcessor : public juce::AudioProcessor
|
||||
{
|
||||
public:
|
||||
HomAudioProcessor();
|
||||
~HomAudioProcessor() override = default;
|
||||
|
||||
// ── AudioProcessor lifecycle ──────────────────────────────────────
|
||||
void prepareToPlay (double sampleRate, int samplesPerBlock) override;
|
||||
void releaseResources() override;
|
||||
|
||||
bool isBusesLayoutSupported (const BusesLayout&) const override;
|
||||
void processBlock (juce::AudioBuffer<float>&, juce::MidiBuffer&) override;
|
||||
|
||||
// ── Boilerplate JUCE host queries ─────────────────────────────────
|
||||
juce::AudioProcessorEditor* createEditor() override;
|
||||
bool hasEditor() const override { return true; }
|
||||
|
||||
const juce::String getName() const override { return "House of Mixtape"; }
|
||||
|
||||
bool acceptsMidi() const override { return false; }
|
||||
bool producesMidi() const override { return false; }
|
||||
bool isMidiEffect() const override { return false; }
|
||||
double getTailLengthSeconds() const override { return 0.0; }
|
||||
|
||||
int getNumPrograms() override { return 1; }
|
||||
int getCurrentProgram() override { return 0; }
|
||||
void setCurrentProgram (int) override {}
|
||||
const juce::String getProgramName (int) override { return {}; }
|
||||
void changeProgramName (int, const juce::String&) override {}
|
||||
|
||||
void getStateInformation (juce::MemoryBlock&) override;
|
||||
void setStateInformation (const void*, int) override;
|
||||
|
||||
// ── House of Mixtape — exposed to the editor ──────────────────────
|
||||
HomApiClient& getApiClient() { return apiClient; }
|
||||
|
||||
private:
|
||||
HomApiClient apiClient;
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (HomAudioProcessor)
|
||||
};
|
||||
Loading…
Reference in New Issue