Skip to content
Permalink
master
Go to file
 
 
Cannot retrieve contributors at this time
441 lines (369 sloc) 10.8 KB
// Part of the Soundplane client software by Madrona Labs.
// Copyright (c) 2013 Madrona Labs LLC. http://www.madronalabs.com
// Distributed under the MIT license: http://madrona-labs.mit-license.org/
#include "SoundplaneOSCOutput.h"
#include "MLTextUtils.h"
#include <thread>
using namespace ml;
const char* kDefaultHostnameString = "localhost";
// --------------------------------------------------------------------------------
#pragma mark SoundplaneOSCOutput
SoundplaneOSCOutput::SoundplaneOSCOutput() :
mCurrentBaseUDPPort(kDefaultUDPPort),
mFrameId(0),
mSerialNumber(0),
mKymaMode(false)
{
try
{
// create buffers for UDP packet streams
mUDPBuffers.resize(kNumUDPPorts);
for(int i=0; i<kNumUDPPorts; ++i)
{
mUDPBuffers[i].resize(kUDPOutputBufferSize);
}
mUDPPacketStreams.resize(kNumUDPPorts);
mUDPSockets.clear();
mUDPSockets.resize(kNumUDPPorts);
}
catch(std::runtime_error err)
{
MLConsole() << "Failed to allocate OSC output: " << err.what() << "\n";
}
}
SoundplaneOSCOutput::~SoundplaneOSCOutput()
{
clear();
}
void SoundplaneOSCOutput::reconnect()
{
setActive(false);
std::string kymaStr("beslime");
int kymaLen = kymaStr.length();
setKymaMode(mHostName.substr(0, kymaLen) == kymaStr);
try
{
MLConsole() << "SoundplaneOSCOutput: connecting to host " << mHostName << " starting at port " << mCurrentBaseUDPPort << " \n";
for(int portOffset = 0; portOffset < kNumUDPPorts; portOffset++)
{
mUDPPacketStreams[portOffset] = std::unique_ptr< osc::OutboundPacketStream >
(new osc::OutboundPacketStream( mUDPBuffers[portOffset].data(), kUDPOutputBufferSize ));
mUDPSockets[portOffset] = std::unique_ptr< UdpTransmitSocket >
(new UdpTransmitSocket(IpEndpointName(mHostName.c_str(), mCurrentBaseUDPPort + portOffset)));
osc::OutboundPacketStream* p = getPacketStreamForOffset(portOffset);
if(!p) return;
UdpTransmitSocket* socket = getTransmitSocketForOffset(portOffset);
if(!socket) return;
*p << osc::BeginBundleImmediate;
*p << osc::BeginMessage( "/t3d/dr" );
*p << (osc::int32)mDataRate;
*p << osc::EndMessage;
*p << osc::EndBundle;
socket->Send( p->Data(), p->Size() );
MLConsole() << " connected to port " << mCurrentBaseUDPPort + portOffset << "\n";
}
setActive(true);
}
catch(std::runtime_error err)
{
MLConsole() << " connect error: " << err.what() << "\n";
mCurrentBaseUDPPort = kDefaultUDPPort;
}
}
int SoundplaneOSCOutput::getKymaMode()
{
return mKymaMode;
}
void SoundplaneOSCOutput::setKymaMode(bool m)
{
MLConsole() << "Kyma mode: " << m << "\n";
mKymaMode = m;
}
void SoundplaneOSCOutput::setActive(bool v)
{
mActive = v;
// reset frame ID
mFrameId = 0;
}
osc::OutboundPacketStream* SoundplaneOSCOutput::getPacketStreamForOffset(int portOffset)
{
osc::OutboundPacketStream* p = mUDPPacketStreams[portOffset].get();
if(p)
{
p->Clear();
}
return p;
}
UdpTransmitSocket* SoundplaneOSCOutput::getTransmitSocketForOffset(int portOffset)
{
return &(*mUDPSockets[portOffset]);
}
const ml::Symbol startFrameSym("start_frame");
const ml::Symbol touchSym("touch");
const ml::Symbol onSym("on");
const ml::Symbol continueSym("continue");
const ml::Symbol offSym("off");
const ml::Symbol controllerSym("controller");
const ml::Symbol xSym("x");
const ml::Symbol ySym("y");
const ml::Symbol xySym("xy");
const ml::Symbol zSym("z");
const ml::Symbol toggleSym("toggle");
const ml::Symbol endFrameSym("end_frame");
const ml::Symbol matrixSym("matrix");
const ml::Symbol nullSym;
void SoundplaneOSCOutput::beginOutputFrame(time_point<system_clock> now)
{
if(!mActive) return;
mFrameTime = now;
// update all voice states
for(int offset=0; offset < kNumUDPPorts; ++offset)
{
for(int voiceIdx=0; voiceIdx < kMaxTouches; ++voiceIdx)
{
Touch& t = (mTouchesByPort[offset])[voiceIdx];
if (t.state == kTouchStateOff)
{
t.state = kTouchStateInactive;
}
}
}
// clear controller message array
for(int i=0; i<kSoundplaneAMaxZones; ++i)
{
mControllersByZone[i] = ZoneMessage{};
}
}
void SoundplaneOSCOutput::processTouch(int i, int offset, const Touch& t)
{
if(!mActive) return;
// store incoming touch by port offset and index
mTouchesByPort[offset][i] = t;
}
void SoundplaneOSCOutput::processController(int zoneID, int h, const ZoneMessage& m)
{
if(!mActive) return;
// store incoming controller by zone ID
mControllersByZone[zoneID] = m;
// store offset into ZoneMessage
mControllersByZone[zoneID].offset = h;
}
void SoundplaneOSCOutput::endOutputFrame()
{
if(!mActive) return;
if(mKymaMode)
{
sendFrameToKyma();
}
else
{
sendFrame();
}
}
void SoundplaneOSCOutput::clear()
{
// TODO should add critical section on mActive, currently we're
// not using it anywhere else so we're OK
setActive(false);
// allow process thread frames to finish
std::this_thread::sleep_for(std::chrono::microseconds(2000));
if(mKymaMode)
{
clearTouches();
sendFrameToKyma();
}
else
{
clearTouches();
sendFrame();
}
}
void SoundplaneOSCOutput::sendFrame()
{
// for each zone, send and clear any controller messages received since last frame
// to the output port for that zone. controller messages are not sent in bundles.
for(int i=0; i<kSoundplaneAMaxZones; ++i)
{
const ZoneMessage c = mControllersByZone[i];
const ZoneMessage d = mSentControllersByZone[i];
if(c != d)
{
int portOffset = c.offset;
// send controller message: /zoneName val1 (val2) on port (kDefaultUDPPort + offset).
osc::OutboundPacketStream* p = getPacketStreamForOffset(portOffset);
UdpTransmitSocket* socket = getTransmitSocketForOffset(portOffset);
if((!p) || (!socket)) return;
TextFragment ctrlStr(TextFragment("/"), c.name.getTextFragment());
*p << osc::BeginMessage( ctrlStr.getText() );
Symbol t = c.type;
if(t == "x")
{
*p << c.x;
}
else if(t == "y")
{
*p << c.y;
}
else if(t == "xy")
{
*p << c.x << c.y;
}
else if(t == "z")
{
*p << c.z;
}
else if(t == "toggle")
{
int t = (c.x > 0.5f);
*p << t;
}
*p << osc::EndMessage;
socket->Send( p->Data(), p->Size() );
mSentControllersByZone[i] = c;
}
}
// for each port, send an OSC bundle containing any touches.
for(int portOffset=0; portOffset<kNumUDPPorts; ++portOffset)
{
// begin OSC bundle for this frame
// timestamp is now stored in the bundle, synchronizing all info for this frame.
osc::OutboundPacketStream* p = getPacketStreamForOffset(portOffset);
UdpTransmitSocket* socket = getTransmitSocketForOffset(portOffset);
if((!p) || (!socket)) return;
osc::uint64 micros = duration_cast<microseconds>(mFrameTime.time_since_epoch()).count();
*p << osc::BeginBundle(micros);
// send frame start message
*p << osc::BeginMessage( "/t3d/frm" );
*p << mFrameId++ << mSerialNumber;
*p << osc::EndMessage;
for(int voiceIdx=0; voiceIdx < kMaxTouches; ++voiceIdx)
{
Touch& t = mTouchesByPort[portOffset][voiceIdx];
if(t.state != kTouchStateInactive)
{
// send dz on first frame of a new touch for note on.
// when scaled the dz values are close to z, so when using z directly
// the error in the first frame is not a problem
constexpr float dzScale = 10.f;
float zOut = (t.state == kTouchStateOn) ? t.dz*dzScale : t.z;
/*
if(t.state == kTouchStateOn)
{
std::cout << "ON " << voiceIdx << " : z=" << t.z << " dz=" << t.dz << "\n";
}
*/
osc::int32 touchID = voiceIdx + 1; // 1-based for OSC
TextFragment address("/t3d/tch", ml::textUtils::naturalNumberToText(touchID));
*p << osc::BeginMessage( address.getText() );
*p << t.x << t.y << zOut << t.note;
*p << osc::EndMessage;
}
}
*p << osc::EndBundle;
socket->Send( p->Data(), p->Size() );
}
}
void SoundplaneOSCOutput::clearTouches()
{
for(int portOffset=0; portOffset<kNumUDPPorts; ++portOffset)
{
for(int voiceIdx=0; voiceIdx < kMaxTouches; ++voiceIdx)
{
Touch& t = mTouchesByPort[portOffset][voiceIdx];
// clear
t.z = 0.f;
t.state = kTouchStateOff;
}
}
}
void SoundplaneOSCOutput::sendFrameToKyma()
{
osc::OutboundPacketStream* p = getPacketStreamForOffset(0);
UdpTransmitSocket* socket = getTransmitSocketForOffset(0);
if((!p) || (!socket)) return;
*p << osc::BeginBundleImmediate;
for(int voiceIdx=0; voiceIdx < kMaxTouches; ++voiceIdx)
{
Touch& t = mTouchesByPort[0][voiceIdx];
osc::int32 touchID = voiceIdx; // 0-based for Kyma
osc::int32 offOn = 1;
if(t.state == kTouchStateOn)
{
offOn = -1;
}
else if(t.state == kTouchStateOff)
{
offOn = 0; // TODO periodically turn off silent voices
}
// note this is called for on and off
if(t.state != kTouchStateInactive)
{
*p << osc::BeginMessage( "/key" );
*p << touchID << offOn << t.note << t.z << t.y;
*p << osc::EndMessage;
}
}
*p << osc::EndBundle;
socket->Send( p->Data(), p->Size() );
}
void SoundplaneOSCOutput::doInfrequentTasks()
{
if(mKymaMode)
{
sendInfrequentDataToKyma();
}
else
{
sendInfrequentData();
}
}
void SoundplaneOSCOutput::sendInfrequentData()
{
for(int portOffset = 0; portOffset < kNumUDPPorts; portOffset++)
{
osc::OutboundPacketStream* p = getPacketStreamForOffset(portOffset);
UdpTransmitSocket* socket = getTransmitSocketForOffset(portOffset);
if((!p) || (!socket)) return;
// send data rate to receiver
*p << osc::BeginBundleImmediate;
*p << osc::BeginMessage( "/t3d/dr" );
*p << (osc::int32)mDataRate;
*p << osc::EndMessage;
*p << osc::EndBundle;
socket->Send( p->Data(), p->Size() );
}
}
void SoundplaneOSCOutput::sendInfrequentDataToKyma()
{
osc::OutboundPacketStream* p = getPacketStreamForOffset(0);
UdpTransmitSocket* socket = getTransmitSocketForOffset(0);
if((!p) || (!socket)) return;
// tell the Kyma that we want to receive info on our listening port
*p << osc::BeginBundleImmediate;
*p << osc::BeginMessage( "/osc/respond_to" );
*p << (osc::int32)kDefaultUDPReceivePort;
*p << osc::EndMessage;
// tell Kyma we are a Soundplane
*p << osc::BeginMessage( "/osc/notify/midi/Soundplane" );
*p << (osc::int32)1;
*p << osc::EndMessage;
*p << osc::EndBundle;
socket->Send( p->Data(), p->Size() );
// send data rate to receiver
*p << osc::BeginBundleImmediate;
*p << osc::BeginMessage( "/t3d/dr" );
*p << (osc::int32)mDataRate;
*p << osc::EndMessage;
*p << osc::EndBundle;
socket->Send( p->Data(), p->Size() );
}
void SoundplaneOSCOutput::processMatrix(const ml::Matrix& m)
{
osc::OutboundPacketStream* p = getPacketStreamForOffset(0);
UdpTransmitSocket* socket = getTransmitSocketForOffset(0);
if((!p) || (!socket)) return;
*p << osc::BeginMessage( "/t3d/matrix" );
*p << osc::Blob( m.getConstBuffer(), m.getSize()*sizeof(float) );
*p << osc::EndMessage;
socket->Send( p->Data(), p->Size() );
}