Geromatic / Midi-Unreal

Midi for Unreal Engine
129 stars 31 forks source link
midi multi-platform playback plugin unreal-engine

Midi-Unreal

Midi for Unreal Engine

Discord: https://discord.gg/yR5wYkX

MarketPlace: https://www.unrealengine.com/marketplace/procedural-midi
Example Project: Updated 2/27/2017

This code provides an interface to read, manipulate, and write MIDI files. "Playback" is supported as a real-time event dispatch system.(Leffelman)

Original Credit goes to Alex Leffelman who created the Android Midi Lib - https://github.com/leffelmania/android-midi-lib Modified for Unreal Engine: Scott Bishel

http://groundsthirteen.webs.com/midi.htm - libraries for c++/c#/obj-c

Features:

[Sending MIDI to/from another program or VST] is possible with a virtual MIDI driver. LoopMidi
--By sending midi data to the virtual midi device...you can have other apps be able to retrieve that data
-You can interact with the midi device by using the Midi Interface Component

** [Shown in Demo Project on marketplace]

Plugin Build Supports:

Installation [Github Version Only / C++ Project Required]

[Caution: careful when you have marketplace and c++ plugins on same UE version as they may conflict with eachother]

Place MidiAsset folder in your Plugins folder in youe Unreal project

Showcase: [Blueprint]

ScreenShot ScreenShot ScreenShot ScreenShot ScreenShot

Example Usage: [Beta-Untested/C++]


// headers
#include <vector>   // std::vector
#include <iostream>     // std::cout, std::ostream, std::ios, std::streambuf
#include <fstream>      // std::filebuf
using namespace std;    // optional

Reading and Writing a MIDI file:

struct membuf : std::streambuf
{
    membuf(char* begin, char* end) {
        this->setg(begin, begin, end);
    }
};

FString path;
TArray<uint8> data;
bool result = FFileHelper::LoadFileToArray(data, path.GetCharArray().GetData());
if (result == 0 || data.Num() == 0)
    return;

char* ptr = (char*)data.GetData();

membuf sbuf(ptr, ptr + data.Num());
std::istream in(&sbuf);

MidiFile midi(in);

...
std::filebuf fb;
fb.open("test.txt", std::ios::out);
std::ostream os(&fb);
midi.writeToFile(os);

Manipulating a MIDI file's data:

Removing a track:

midi.removeTrack(2);

Removing any event that is not a note from track 1:

MidiTrack& track = *midi.getTracks()[1];

std::vector<MidiEvent*>::iterator it = track.getEvents().begin();
std::vector<MidiEvent*> eventsToRemove;

while (it)
{
    MidiEvent* _event = *it;

    if (!(_event->getType() == ChannelEvent::NOTE_ON) && !(_event->getType() == ChannelEvent::NOTE_OFF))
    {
        eventsToRemove.Add(_event);
    }
    it++;
}
for (int i = 0; i < eventsToRemove.Num(); i++) {
    track.removeEvent(eventsToRemove[i]);
}

Reducing the tempo by half:

    MidiTrack& track = *midi.getTracks()[0];

    std::vector<MidiEvent*>::iterator it = track.getEvents().begin();

    while (it)
    {
        MidiEvent* _event = *it;

        if (_event->getType() == MetaEvent::TEMPO)
        {
            Tempo* tempoEvent = (Tempo*)_event;
            tempoEvent->setBpm(tempoEvent->getBpm() / 2);
        }
        it++;
    }

Composing a new MIDI file:

// 1. Create some MidiTracks
MidiTrack& tempoTrack = *new MidiTrack();
MidiTrack& noteTrack = *new MidiTrack();

// 2. Add events to the tracks
// Track 0 is the tempo map
TimeSignature& ts = *new TimeSignature();
ts.setTimeSignature(4, 4, TimeSignature::DEFAULT_METER, TimeSignature::DEFAULT_DIVISION);

Tempo& tempo = *new Tempo();
tempo.setBpm(228);

tempoTrack.insertEvent(&ts);
tempoTrack.insertEvent(&tempo);

// Track 1 will have some notes in it
const int NOTE_COUNT = 80;

for(int i = 0; i < NOTE_COUNT; i++)
{
    int channel = 0;
    int pitch = 1 + i;
    int velocity = 100;
    long tick = i * 480;
    long duration = 120;

    noteTrack.insertNote(channel, pitch, velocity, tick, duration);
}

// 3. Create a MidiFile with the tracks we created
std::vector<MidiTrack*>& tracks = *new std::vector<MidiTrack*>();
tracks.push_back(&tempoTrack);
tracks.push_back(&noteTrack);

MidiFile& midi = *new MidiFile(MidiFile::DEFAULT_RESOLUTION, tracks);

// 4. Write the MIDI data to a file
std::filebuf fb;
fb.open("test.txt", std::ios::out);
std::ostream os(&fb);
midi.writeToFile(os);

Listening for and processing MIDI events

// Create a new MidiProcessor:
MidiProcessor processor;
processor.load(midi);

// Register for the events you're interested in:
EventPrinter ep;
processor.setListener(&ep);

// Start the processor:
processor.start();
// This class will print any event it receives to the console
class EventPrinter: public MidiEventListener
{

public:
    EventPrinter()
    {
    }

    void onStart(bool fromBeginning)
    {
        if(fromBeginning)
        {
            UE_LOG(LogTemp, Display, TEXT("Started!"));
        }
        else
        {
            UE_LOG(LogTemp, Display, TEXT("Resumed!"));
        }
    }

    void onEvent(MidiEvent* _event, long ms, int trackID)
    {
        FString layerName(_event->toString().c_str());
    UE_LOG(LogTemp, Display, TEXT("received event: %s"), *layerName);
    }

    void onStop(bool finished)
    {
        if(finished)
        {
            UE_LOG(LogTemp, Display, TEXT("Finished!"));
        }
        else
        {
            UE_LOG(LogTemp, Display, TEXT("Paused!"));
        }
    }
};