Skip to content

Instantly share code, notes, and snippets.

@adamski
Created December 9, 2017 21:12
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save adamski/b42eb8f91910fe28a017458e2edad5d3 to your computer and use it in GitHub Desktop.
Save adamski/b42eb8f91910fe28a017458e2edad5d3 to your computer and use it in GitHub Desktop.
JUCE MTC Classes
//
// MTCEmitter.cpp
// XDAT
//
// Created by Arvid Rosén on 2012-02-09.
// Copyright (c) 2012 Arvid Rosén. All rights reserved.
//
#include <iostream>
#include "MTCEmitter.h"
#include "SMPTE.h"
AbstractFifo SMPTE::fifo(1024);
double SMPTE::positionInSeconds(0.0);
MidiMessage::SmpteTimecodeType SMPTE::type(MidiMessage::fps30);
double SMPTE::fps(30);
int SMPTE::buffer[] = {};
MTCEmitter::MTCEmitter()
:Thread("SMPTE Updater")
{
smpteTimeCodeType = MidiMessage::fps30;
framesPerSecond = 30;
interval = (1000.0/(4.0*framesPerSecond));
delta = 0.0;
currentQuarterFrame = 0;
backwards = false;
SMPTE::init(smpteTimeCodeType);
}
MTCEmitter::~MTCEmitter()
{
stopThread(2000);
}
bool MTCEmitter::open(const int devNum=0)
{
midiOutput = MidiOutput::openDevice(devNum);
DBG("Opening MTC device: " << MidiOutput::getDevices()[devNum]);
if(!midiOutput)
return false;
reset();
return true;
}
bool MTCEmitter::start()
{
startThread(6);
if(!midiOutput)
return false;
return true;
}
void MTCEmitter::stop()
{
if(!midiOutput)
return;
stopThread(500);
}
void MTCEmitter::run()
{
while (! threadShouldExit())
{
// sleep a bit so the threads don't all grind the CPU to a halt..
const int timeToWait = roundToInt(interval + delta);
delta = timeToWait - interval;
wait (timeToWait);
midiOutput->sendMessageNow(nextMidiMsg);
postDisplayUpdate();
prepareNextMessage(false);
}
}
// Post message to display component, to update its SMPTE display.
void MTCEmitter::postDisplayUpdate()
{
int hour,min,sec,frame;
SMPTE::getSMPTEValue(hour, min, sec, frame);
SMPTEMessage *msg = new SMPTEMessage();
msg->intParameter1 = hour;
msg->intParameter2 = (min<<8) + sec;
msg->intParameter3 = frame;
displayOwner->postMessage(msg);
}
// Send full MTC goto message
void MTCEmitter::sendGoto()
{
prepareNextMessage(true);
midiOutput->sendMessageNow(nextMidiMsg);
postDisplayUpdate();
}
// Re-init SMPTE counter and update possition
void MTCEmitter::reset()
{
SMPTE::init(smpteTimeCodeType);
// Send full message
sendGoto();
}
void MTCEmitter::prepareNextMessage(const bool fullFrame=false)
{
int val;
int hour,min,sec,frame;
if(fullFrame) {
SMPTE::update(samplerate);
SMPTE::getSMPTEValue(hour, min, sec, frame);
nextMidiMsg = MidiMessage::fullFrame(hour, min, sec, frame, smpteTimeCodeType);
} else {
SMPTE::getSMPTEValue(hour, min, sec, frame);
switch(currentQuarterFrame) {
case 0: val = frame & 0x0F; break;
case 1: val = (frame & 0x10) >> 4; break;
case 2: val = sec & 0x0F; break;
case 3: val = (sec & 0x30) >> 4; break;
case 4: val = min & 0x0F; break;
case 5: val = (min & 0x30) >> 4; break;
case 6: val = hour & 0x0F; break;
case 7: val = ((hour & 0x10) >> 4) | ((SMPTE::type & 0x03) << 1); break;
}
nextMidiMsg = MidiMessage::quarterFrame(currentQuarterFrame, val);
if(backwards)
currentQuarterFrame--;
else
currentQuarterFrame++;
if(currentQuarterFrame < 0) {
currentQuarterFrame = 7;
}
if(currentQuarterFrame > 7) {
currentQuarterFrame = 0;
SMPTE::update(samplerate);
}
}
}
//
// MTCEmitter.h
// XDAT
//
// Created by Arvid Rosén on 2012-02-09.
// Copyright (c) 2012 Arvid Rosén. All rights reserved.
//
#ifndef XDAT_MTCEmitter_h
#define XDAT_MTCEmitter_h
#include "../JuceLibraryCode/JuceHeader.h"
class MTCEmitter: public Thread
{
public:
MTCEmitter();
~MTCEmitter();
bool start();
void stop();
bool open(const int devNum);
void reset();
void sendGoto();
void setSamplerate(const double samplerate_) { samplerate = samplerate_; }
void prepareNextMessage(const bool fullFrame);
void run();
void postDisplayUpdate();
MessageListener *displayOwner;
private:
MidiMessage nextMidiMsg;
ScopedPointer<MidiOutput> midiOutput;
int currentQuarterFrame;
float interval;
float delta;
bool backwards;
double samplerate;
double framesPerSecond;
MidiMessage::SmpteTimecodeType smpteTimeCodeType;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MTCEmitter);
};
#endif
//
// SMPTE.h
// XDAT
//
// Created by Arvid Rosén on 2012-02-28.
// Copyright (c) 2012 Arvid Rosén. All rights reserved.
//
#ifndef XDAT_SMPTE_h
#define XDAT_SMPTE_h
#include "../JuceLibraryCode/JuceHeader.h"
class SMPTEMessage: public Message
{
public:
int intParameter1;
int intParameter2;
int intParameter3;
};
class SMPTE
{
public:
static MidiMessage::SmpteTimecodeType type;
void static init(const MidiMessage::SmpteTimecodeType type_)
{
positionInSeconds = 0.0;
fifo.reset();
if(type==MidiMessage::fps30)
fps = 30;
if(type==MidiMessage::fps25)
fps = 25;
if(type==MidiMessage::fps24)
fps = 24;
}
// Add samples that are not yet used to update the current time
static void addSamples (const int samples)
{
int start1, size1, start2, size2;
fifo.prepareToWrite (1, start1, size1, start2, size2);
if (size1 > 0)
buffer[start1]=samples;
if (size2 > 0)
buffer[start2]=samples;
fifo.finishedWrite (size1 + size2);
}
// Update time using the added samples
static void update (const double samplerate)
{
if(samplerate <= 0)
return;
int start1, size1, start2, size2;
int newSamples=0;
fifo.prepareToRead (fifo.getNumReady(), start1, size1, start2, size2);
for(int i=0; i<size1; i++)
newSamples += buffer[start1+i];
for(int i=0; i<size2; i++)
newSamples += buffer[start2+i];
fifo.finishedRead (size1 + size2);
positionInSeconds += double(newSamples)/samplerate;
}
static void getSMPTEValue(int &hour, int &min, int &sec, int &frames)
{
double tmp = positionInSeconds;
int q;
q = floor(tmp/(3600.0));
tmp -= q*3600.0;
hour = q;
q = floor(tmp/(60.0));
tmp -= q*60.0;
min = q;
q = floor(tmp);
tmp -= q;
sec = q;
frames = floor(tmp*fps);
}
static void setSMPTEValue(const int hour, const int min, const int sec, const int frames)
{
positionInSeconds = hour*3600 + min*60 + sec + double(frames)/fps;
}
static int getNumReady()
{
return fifo.getNumReady();
}
static double getPosition()
{
return positionInSeconds;
}
private:
static double positionInSeconds;
static double fps;
static AbstractFifo fifo;
static int buffer[1024];
};
#endif
@izzyreal
Copy link

Cool stuff! Do you have an updated version of this for JUCE7? A usage example in modern JUCE would also be great. In particular I'm curious with regard to MTCEmitter::open(...) and its call to MidiOutput::openDevice(devNum), which is now deprecated in JUCE7.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment