Rank: Advanced Member
Groups: Registered
Joined: 10/28/2015(UTC) Posts: 183
Thanks: 7 times Was thanked: 15 time(s) in 14 post(s)
|
I have no doubt that vMix will at some point support MIDI out to allow us to use our indicators on our MIDI devices as a tally board, but in the meantime this workaround is tested and works wonderfully. The code will work out of the boc for the APC40, other devices will need to modify their channels and notes. This will dynamically light up clips on your board as shots are added to vMix, it will also re-order lights if tracks are re-ordered in vMix. Active overlays will also show as live. Uses the midi-dot-net project from https://code.google.com/p/midi-dot-net/ to simplify the MIDI out process. Code:using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Windows.Forms;
using Midi;
using System.Threading;
using System.Net;
using System.Text.RegularExpressions;
namespace APC40_Shot_Selector
{
public partial class Form1 : Form
{
List<shotKey> shots;
Thread shotListThread, shotStatusThread;
public Form1()
{
InitializeComponent();
ExtensionMethods.outputDevice = OutputDevice.InstalledDevices.FirstOrDefault(d => d.Name == "Akai APC40");
ExtensionMethods.outputDevice.Open();
shots = new List<shotKey>();
shots.Add(new shotKey(1, Channel.Channel1, Pitch.F3));
shots.Add(new shotKey(2, Channel.Channel2, Pitch.F3));
shots.Add(new shotKey(3, Channel.Channel3, Pitch.F3));
shots.Add(new shotKey(4, Channel.Channel4, Pitch.F3));
shots.Add(new shotKey(5, Channel.Channel5, Pitch.F3));
shots.Add(new shotKey(6, Channel.Channel6, Pitch.F3));
shots.Add(new shotKey(7, Channel.Channel7, Pitch.F3));
shots.Add(new shotKey(8, Channel.Channel8, Pitch.F3));
shots.Add(new shotKey(9, Channel.Channel1, Pitch.FSharp3));
shots.Add(new shotKey(10, Channel.Channel2, Pitch.FSharp3));
shots.Add(new shotKey(11, Channel.Channel3, Pitch.FSharp3));
shots.Add(new shotKey(12, Channel.Channel4, Pitch.FSharp3));
shots.Add(new shotKey(13, Channel.Channel5, Pitch.FSharp3));
shots.Add(new shotKey(14, Channel.Channel6, Pitch.FSharp3));
shots.Add(new shotKey(15, Channel.Channel7, Pitch.FSharp3));
shots.Add(new shotKey(16, Channel.Channel8, Pitch.FSharp3));
shots.Add(new shotKey(17, Channel.Channel1, Pitch.G3));
shots.Add(new shotKey(18, Channel.Channel2, Pitch.G3));
shots.Add(new shotKey(19, Channel.Channel3, Pitch.G3));
shots.Add(new shotKey(20, Channel.Channel4, Pitch.G3));
shots.Add(new shotKey(21, Channel.Channel5, Pitch.G3));
shots.Add(new shotKey(22, Channel.Channel6, Pitch.G3));
shots.Add(new shotKey(23, Channel.Channel7, Pitch.G3));
shots.Add(new shotKey(24, Channel.Channel8, Pitch.G3));
shots.Add(new shotKey(25, Channel.Channel1, Pitch.GSharp3));
shots.Add(new shotKey(26, Channel.Channel2, Pitch.GSharp3));
shots.Add(new shotKey(27, Channel.Channel3, Pitch.GSharp3));
shots.Add(new shotKey(28, Channel.Channel4, Pitch.GSharp3));
shots.Add(new shotKey(29, Channel.Channel5, Pitch.GSharp3));
shots.Add(new shotKey(30, Channel.Channel6, Pitch.GSharp3));
shots.Add(new shotKey(31, Channel.Channel7, Pitch.GSharp3));
shots.Add(new shotKey(32, Channel.Channel8, Pitch.GSharp3));
shots.Add(new shotKey(33, Channel.Channel1, Pitch.A3));
shots.Add(new shotKey(34, Channel.Channel2, Pitch.A3));
shots.Add(new shotKey(35, Channel.Channel3, Pitch.A3));
shots.Add(new shotKey(36, Channel.Channel4, Pitch.A3));
shots.Add(new shotKey(37, Channel.Channel5, Pitch.A3));
shots.Add(new shotKey(38, Channel.Channel6, Pitch.A3));
shots.Add(new shotKey(39, Channel.Channel7, Pitch.A3));
shots.Add(new shotKey(40, Channel.Channel8, Pitch.A3));
shotListThread = new Thread(shotListThreadMethod);
shotListThread.Start();
shotStatusThread = new Thread(shotStatusThreadMethod);
shotStatusThread.Start();
}
public void shotListThreadMethod()
{
while (true)
{
Thread.Sleep(2000);
string shotListHTML = (new WebClient()).DownloadString("http://127.0.0.1:8088/tally");
List<LinkItem> matchingLinks = LinkFinder.Find(shotListHTML).Where(link => link.Class == "tallyLink").ToList();
for (int i=0;i < 40;i++)
{
if (i < matchingLinks.Count())
shots[i].guid = new Guid(matchingLinks[i].Href.Replace("/tally/?key=", ""));
else
shots[i].guid = new Guid();
}
}
}
public void shotStatusThreadMethod()
{
while (true)
{
Thread.Sleep(100);
foreach (shotKey shot in shots.Where(s => s.guid != default(Guid)).ToList())
{
string shotStatusHTML = (new WebClient()).DownloadString("http://127.0.0.1:8088/tallyupdate/?key="+ shot.guid.ToString());
if (shotStatusHTML == "tallyChange(\"#1a3c75\");")
{
shot.live = false;
shot.preview = false;
}
else if (shotStatusHTML == "tallyChange(\"#ff8c00\");")
{
shot.live = false;
shot.preview = true;
}
else if (shotStatusHTML == "tallyChange(\"#006400\");")
{
shot.live = true;
shot.preview = false;
}
}
}
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
shotListThread.Abort();
shotStatusThread.Abort();
}
}
public struct LinkItem
{
public string Href;
public string Text;
public string Class;
public override string ToString()
{
return Href + "\n\t" + Text;
}
}
static class LinkFinder
{
public static List<LinkItem> Find(string file)
{
List<LinkItem> list = new List<LinkItem>();
MatchCollection m1 = Regex.Matches(file, @"(<a.*?>.*?</a>)",
RegexOptions.Singleline);
foreach (Match m in m1)
{
string value = m.Groups[1].Value;
LinkItem i = new LinkItem();
Match m2 = Regex.Match(value, @"href=\""(.*?)\""",
RegexOptions.Singleline);
if (m2.Success)
{
i.Href = m2.Groups[1].Value;
}
Match m3 = Regex.Match(value, @"class=\""(.*?)\""",
RegexOptions.Singleline);
if (m2.Success)
{
i.Class = m3.Groups[1].Value;
}
string t = Regex.Replace(value, @"\s*<.*?>\s*", "",
RegexOptions.Singleline);
i.Text = t;
list.Add(i);
}
return list;
}
}
public class shotKey
{
private Guid _guid;
public Guid guid {
get { return _guid; }
set
{
_guid = value;
if (_guid != default(Guid))
{
if (!preview && !live)
this.setValue(5);
if (preview)
this.setValue(1);
if (live)
this.setValue(3);
}
else
{
this.setValue(0);
}
}
}
public int shotID;
public Channel midiChannel;
public Pitch midiPitch;
private bool _preview;
private bool _live;
public bool preview {
get { return _preview; }
set {
_preview = value;
if (_guid != default(Guid))
{
if (!preview && !live)
this.setValue(5);
if (preview)
this.setValue(1);
if (live)
this.setValue(3);
} else
{
this.setValue(0);
}
}
}
public bool live
{
get { return _live; }
set
{
_live = value;
if (_guid != default(Guid))
{
if (!preview && !live)
this.setValue(5);
if (preview)
this.setValue(1);
if (live)
this.setValue(3);
} else
{
this.setValue(0);
}
}
}
public shotKey(int id, Channel channel, Pitch pitch)
{
midiChannel = channel;
midiPitch = pitch;
preview = false;
live = false;
shotID = id;
}
}
public static class ExtensionMethods
{
public static OutputDevice outputDevice;
public static void setValue(this shotKey shot, int value)
{
outputDevice.SendNoteOn(shot.midiChannel, shot.midiPitch, value);
}
}
}
There are no setting, no nothing really, just quick and dirty code. jhebbel attached the following image(s): 20151028_193834.jpg (2,546kb) downloaded 133 time(s).You cannot view/download attachments. Try to login or register.
|