/******************************************************************************* 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: MidiRecordingDemo version: 0.0.1 vendor: Tracktion website: www.tracktion.com description: This example simply creates a new project and records from one midi input. It also allows a synth plgin to be added to the track dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats, juce_audio_processors, juce_audio_utils, juce_core, juce_data_structures, juce_dsp, juce_events, juce_graphics, juce_gui_basics, juce_gui_extra, juce_osc, tracktion_engine exporters: linux_make, vs2017, xcode_iphone, xcode_mac moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1, JUCE_PLUGINHOST_AU=1, JUCE_PLUGINHOST_VST3=1 type: Component mainClass: MidiRecordingDemo END_JUCE_PIP_METADATA *******************************************************************************/ #pragma once #include "common/Utilities.h" #include "common/Components.h" #include "common/PluginWindow.h" //============================================================================== class MidiRecordingDemo : public Component, private ChangeListener { public: //============================================================================== MidiRecordingDemo() { settingsButton.onClick = [this] { EngineHelpers::showAudioDeviceSettings (engine); }; pluginsButton.onClick = [this] { DialogWindow::LaunchOptions o; o.dialogTitle = TRANS("Plugins"); o.dialogBackgroundColour = Colours::black; o.escapeKeyTriggersCloseButton = true; o.useNativeTitleBar = true; o.resizable = true; o.useBottomRightCornerResizer = true; auto v = new PluginListComponent (engine.getPluginManager().pluginFormatManager, engine.getPluginManager().knownPluginList, engine.getTemporaryFileManager().getTempFile ("PluginScanDeadMansPedal"), te::getApplicationSettings()); v->setSize (800, 600); o.content.setOwned (v); o.launchAsync(); }; newEditButton.onClick = [this] { createOrLoadEdit(); }; updatePlayButtonText(); updateRecordButtonText(); editNameLabel.setJustificationType (Justification::centred); Helpers::addAndMakeVisible (*this, { &settingsButton, &pluginsButton, &newEditButton, &playPauseButton, &showEditButton, &recordButton, &addMIDIClip, &renderMidi, &newTrackButton, &deleteButton, &editNameLabel }); deleteButton.setEnabled (false); auto d = File::getSpecialLocation (File::tempDirectory).getChildFile ("MidiRecordingDemo"); d.createDirectory(); auto f = Helpers::findRecentEdit (d); if (f.existsAsFile()) createOrLoadEdit (f); else createOrLoadEdit (d.getNonexistentChildFile ("Test", ".tracktionedit", false)); selectionManager.addChangeListener (this); setupButtons(); setSize (600, 400); } ~MidiRecordingDemo() { te::EditFileOperations (*edit).save (true, true, false); engine.getTemporaryFileManager().getTempDirectory().deleteRecursively(); } void renderMidiClip(tracktion_engine::Clip* c, int trackIdx) { auto& edit = c->edit; auto clipPos = c->getPosition(); BigInteger tracksToDo; tracksToDo.setBit (trackIdx); Array clips; clips.add (c); const bool usePlugins = true; File dir = File::getSpecialLocation (File::userDesktopDirectory); auto f = dir.getNonexistentChildFile (File::createLegalFileName (c->getName()), ".wav"); DBG(f.getFullPathName()); DBG(c->getName()); DBG(tracksToDo.toInteger()); tracktion_engine::Renderer::renderToFile (TRANS("Render Clip"), f, edit, clipPos.time, tracksToDo, usePlugins, clips, false); edit.getTransport().restartAllTransports(engine, true); } void renderAllMidiClips() { auto tracks = tracktion_engine::getAllTracks(*edit); int trackIdx = 0; for (auto track : tracks) { if (auto audioTrack = dynamic_cast(track)) { for (auto clip : audioTrack->getClips()) { DBG(track->getName()); if (auto midiClip = dynamic_cast(clip)) { renderMidiClip(clip, trackIdx); } } } trackIdx++; } } void clearClipsFromTrack(tracktion_engine::ClipTrack* track) { if (auto audioTrack = dynamic_cast(track)) { for (auto clip : audioTrack->getClips()) { if (auto midiClip = dynamic_cast(clip)) { midiClip->removeFromParentTrack(); } } } } void insertMidiClip (const File& file) { //auto clipLength = edit->tempoSequence.beatsToTime (1000); auto tracks = tracktion_engine::getClipTracks(*edit); auto midiTrack = tracks[3]; if (midiTrack->containsAnyMIDIClips()) { clearClipsFromTrack(midiTrack); } if (auto fin = std::unique_ptr (file.createInputStream())) { juce::MidiFile mf; mf.readFrom (*fin); mf.convertTimestampTicksToSeconds(); juce::MidiMessageSequence sequence; for (int i = 0; i < mf.getNumTracks(); ++i) sequence.addSequence (*mf.getTrack (i), 0.0, 0.0, 1.0e6); sequence.updateMatchedPairs(); int ch10Count = 0, allCount = 0; for (auto m : sequence) { auto msg = m->message; if (msg.isNoteOn()) { allCount++; if (msg.getChannel() == 10) ch10Count++; } } if (auto mc = midiTrack->insertMIDIClip ({ 0.0, 1.0 }, nullptr)) { mc->mergeInMidiSequence (sequence, tracktion_engine::MidiList::NoteAutomationType::none); } } DBG(midiTrack->getNumTrackItems()); return; } //============================================================================== void paint (Graphics& g) override { g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId)); } void resized() override { auto r = getLocalBounds(); int w = r.getWidth() / 7; auto topR = r.removeFromTop (30); auto topL = r.removeFromTop (40); settingsButton.setBounds (topR.removeFromLeft (w).reduced (2)); pluginsButton.setBounds (topR.removeFromLeft (w).reduced (2)); newEditButton.setBounds (topR.removeFromLeft (w).reduced (2)); playPauseButton.setBounds (topR.removeFromLeft (w).reduced (2)); recordButton.setBounds (topR.removeFromLeft (w).reduced (2)); addMIDIClip.setBounds (topL.removeFromLeft (w).reduced (2)); renderMidi.setBounds (topL.removeFromLeft (w).reduced (2)); showEditButton.setBounds (topL.removeFromLeft (w).reduced (2)); newTrackButton.setBounds (topL.removeFromLeft (w).reduced (2)); deleteButton.setBounds (topL.removeFromLeft (w).reduced (2)); topR = r.removeFromTop (30); editNameLabel.setBounds (topR); if (editComponent != nullptr) editComponent->setBounds (r); } private: //============================================================================== te::Engine engine { ProjectInfo::projectName, std::make_unique(), nullptr }; te::SelectionManager selectionManager { engine }; std::unique_ptr edit; std::unique_ptr editComponent; TextButton settingsButton { "Settings" }, pluginsButton { "Plugins" }, newEditButton { "New" }, playPauseButton { "Play" }, showEditButton { "Show Edit" }, newTrackButton { "New Track" }, deleteButton { "Delete" }, recordButton { "Record" }, addMIDIClip{ "Add Midi Clip" }, renderMidi{ "Render" }; Label editNameLabel { "No Edit Loaded" }; ToggleButton showWaveformButton { "Show Waveforms" }; //============================================================================== void setupButtons() { playPauseButton.onClick = [this] { EngineHelpers::togglePlay (*edit); }; recordButton.onClick = [this] { bool wasRecording = edit->getTransport().isRecording(); EngineHelpers::toggleRecord (*edit); if (wasRecording) te::EditFileOperations (*edit).save (true, true, false); }; addMIDIClip.onClick = [this] { FileChooser fileChooser("Find a MIDI file", File(), "*.midi"); if (fileChooser.browseForFileToOpen()) { insertMidiClip(fileChooser.getResult()); } }; renderMidi.onClick = [this] { renderAllMidiClips(); }; newTrackButton.onClick = [this] { edit->ensureNumberOfAudioTracks (getAudioTracks (*edit).size() + 1); }; deleteButton.onClick = [this] { auto sel = selectionManager.getSelectedObject (0); if (auto clip = dynamic_cast (sel)) { clip->removeFromParentTrack(); } else if (auto track = dynamic_cast (sel)) { if (! (track->isMarkerTrack() || track->isTempoTrack() || track->isChordTrack())) edit->deleteTrack (track); } else if (auto plugin = dynamic_cast (sel)) { plugin->deleteFromParent(); } }; showWaveformButton.onClick = [this] { auto& evs = editComponent->getEditViewState(); evs.drawWaveforms = ! evs.drawWaveforms.get(); showWaveformButton.setToggleState (evs.drawWaveforms, dontSendNotification); }; } void updatePlayButtonText() { if (edit != nullptr) playPauseButton.setButtonText (edit->getTransport().isPlaying() ? "Stop" : "Play"); } void updateRecordButtonText() { if (edit != nullptr) recordButton.setButtonText (edit->getTransport().isRecording() ? "Abort" : "Record"); } void createOrLoadEdit (File editFile = {}) { if (editFile == File()) { FileChooser fc ("New Edit", File::getSpecialLocation (File::userDocumentsDirectory), "*.tracktionedit"); if (fc.browseForFileToSave (true)) editFile = fc.getResult(); else return; } selectionManager.deselectAll(); editComponent = nullptr; if (editFile.existsAsFile()) edit = std::make_unique (engine, ValueTree::fromXml (editFile.loadFileAsString()), te::Edit::forEditing, nullptr, 0); else edit = std::make_unique (engine, te::createEmptyEdit (engine), te::Edit::forEditing, nullptr, 0); edit->editFileRetriever = [editFile] { return editFile; }; edit->playInStopEnabled = true; auto& transport = edit->getTransport(); transport.addChangeListener (this); editNameLabel.setText (editFile.getFileNameWithoutExtension(), dontSendNotification); showEditButton.onClick = [this, editFile] { te::EditFileOperations (*edit).save (true, true, false); editFile.revealToUser(); }; if (! editFile.existsAsFile()) createTracksAndAssignInputs(); else edit->getTransport().ensureContextAllocated(); te::EditFileOperations (*edit).save (true, true, false); editComponent = std::make_unique (*edit, selectionManager); editComponent->getEditViewState().showFooters = true; editComponent->getEditViewState().showMidiDevices = true; editComponent->getEditViewState().showWaveDevices = false; addAndMakeVisible (*editComponent); } void changeListenerCallback (ChangeBroadcaster* source) override { if (edit != nullptr && source == &edit->getTransport()) { updatePlayButtonText(); updateRecordButtonText(); } else if (source == &selectionManager) { auto sel = selectionManager.getSelectedObject (0); deleteButton.setEnabled (dynamic_cast (sel) != nullptr || dynamic_cast (sel) != nullptr || dynamic_cast (sel)); } } void createTracksAndAssignInputs() { auto& dm = engine.getDeviceManager(); for (int i = 0; i < dm.getNumMidiInDevices(); i++) { if (auto mip = dm.getMidiInDevice (i)) { mip->setEndToEndEnabled (true); mip->setEnabled (true); } } edit->getTransport().ensureContextAllocated(); int trackNum = 0; for (auto instance : edit->getAllInputDevices()) { if (instance->getInputDevice().getDeviceType() == te::InputDevice::physicalMidiDevice) { if (auto t = EngineHelpers::getOrInsertAudioTrackAt (*edit, trackNum)) { instance->setTargetTrack (*t, 0, true); instance->setRecordingEnabled (*t, true); trackNum++; } } } edit->restartPlayback(); } JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiRecordingDemo) };