[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[offlist] Re: [sc-users] enttec dmx pro



On Wednesday 17 June 2009 16:31:18 you wrote:
> helo,
>
> has anyone ever used the enttec dmx pro interface from within sc? i am
> currently using wtf2osc and it works with some dmx clients but not with
> others. somehow the signal is strange. the "reset" period is too long.

I have these classes, which I made quite a while ago but never actually tested 
with the device.
Let me know if that works for you :)

sincerely,
Marije

> thanks!
> wilm
>
> _______________________________________________
> sc-users mailing list
>
> info (subscription, etc.):
> http://www.beast.bham.ac.uk/research/sc_mailing_lists.shtml archive:
> https://listarc.bham.ac.uk/marchives/sc-users/
> search: https://listarc.bham.ac.uk/lists/sc-users/search/



(
// create a new instance of DMX
d = DMX.new;
// create a DMX device
e = EntTecDMXUSBPro.new( 0 );
// set the device for DMX
d.device = e;

// create an empty cue:
c = DMXCue.new;

// add the cue to DMX:
d.cues.add( c );

c.put( 5, 0.5 );
c.put( 20, 1 );

// set the current cue:
d.currentCue = d.cues.at( 0 );


a = DMXSubCue.new;
a.put( 3, 0.1 );
a.put( 15, 1 );
)

v = DMXGui( d );

a.data

d.currentCue.data
d.map
d.map.findKeyForValue( 3 )

d.map.at(\table)

d.map.put( \table, 2);

d.fade( DMXCue.new, 10.0 );
c = DMXCue.new
c.data
d.fade( c, 10.0 );

b = d.currentCue.deepCopy;
b.merge( a );
b.data
b.data * 0.8 + d.currentCue.data * 0.2

c.data

a.data
a.data.indices

GUI.scrollPane

Tdef( \dmxfade ).envir
Tdef( \dmxfade ).envir.fadeval

Tdef( \dmxfade ).set( \speed, 2 );
// class to create a GUI for DMX

DMXGui{
	classvar <>xposScreen=0, <>yposScreen=20;
	classvar <counter = 0;
	var <>dmx;
	var <sliderview, <cueview;
	var <showconsole;
	var <>w, <watcher;

	*new { |dmx, w| 
		^super.new.w_(w).dmx_(dmx).init;
	}
	
	init { 
		var f, xsize, ysize;
		var ncview, rmview, clview;
		counter = counter + 1;

		xsize = 300;
		ysize = 105+130+10;
	
		w = w ?? { 
			w = GUI.window.new("DMX Control", Rect(xposScreen, yposScreen, xsize + 10, ysize)).front;
			//w.view.background_(Color.black); 
			w.view.decorator = FlowLayout(Rect(4, 4, w.bounds.width, w.bounds.height), 2@2, 2@2);
			w;
		};

		// unclutter the windows on the screen:
		yposScreen = yposScreen + 160;
		if ( yposScreen > 700,
			{ yposScreen = 20; xposScreen = xposScreen + xsize + 10;
				if ( xposScreen > 900,
					{ xposScreen = 0; });
			});

		showconsole = GUI.button.new( w, Rect( 0, 0, xsize-5, 20 ) ).states_( [ ["Show console"] ] ).action_( { DMXConsole.new( dmx ) } );

		//		GUI.staticText.new(w, Rect(0, 0, xsize - 2, 20)).string_("WiiMote" + wiimote.id + wiimote.address )
		//			.align_(0);
		//.background_(labelColor); 

		cueview = GUI.compositeView.new( w, Rect( 5, 30, 205, 130 ));
		//		rm = WiiRemoteGUI.new( rmview, wiimote, 30 );

		//		sliderview = GUI.compositeView.new( w, Rect( 5, 160, 205, 105 ));
		//		nc = WiiNunchukGUI.new( ncview, wiimote, 160 );

		watcher = SkipJack.new( { this.updateVals }, 0.1, { w.isClosed }, (\dmx_gui_ ++ counter));
		watcher.start;
	}

	updateVals { 
		{ 
			//			rm.updateVals;
			//			nc.updateVals;
			//			cl.updateVals;
		}.defer;
	}

	hide{
		if ( GUI.scheme.id == \swing,
			{
				w.visible_( false );
			},{
				w.close;
			});
		watcher.stop;
	}

	show{
		if ( GUI.scheme.id == \swing,
			{
				w.visible_( true );
			});
		watcher.start;
	}

}

DMXConsole{
	classvar <>xposScreen=0, <>yposScreen=20;
	classvar <counter = 0;
	var <>dmx;
	var <sliderviews;
	var <showconsole;
	var <tabpane;
	var <sliders;
	var <>w, <watcher;

	*new { |dmx, w| 
		^super.new.w_(w).dmx_(dmx).init;
	}
	
	init { 
		var f, xsize, ysize;
		counter = counter + 1;

		xsize = 16*37 + 15;
		ysize = 210;
	
		w = w ?? { 
			w = GUI.window.new("DMX Console", Rect(xposScreen, yposScreen, xsize + 10, ysize)).front;
			w.view.decorator = FlowLayout(Rect(4, 4, w.bounds.width, w.bounds.height), 2@2, 2@2);
			w;
		};

		// unclutter the windows on the screen:
		yposScreen = yposScreen + 160;
		if ( yposScreen > 700,
			{ yposScreen = 20; xposScreen = xposScreen + xsize + 10;
				if ( xposScreen > 900,
					{ xposScreen = 0; });
			});

		//sliderview = GUI.scrollPane.new( w, w.view.bounds )
		tabpane = JSCTabbedPane.new( w, Rect( w.view.bounds.left, w.view.bounds.top, w.view.bounds.width-15, w.view.bounds.height-15) );
		//.verticalScrollBarShown_( \never ).horizontalScrollBarShown_( \auto );

		sliders = Array.new;
		sliderviews = 16.collect{ |it2|
			var gv;
			gv = GUI.compositeView.new( tabpane, Rect( 0, 0, 16*35 + 5, 165) );
			sliders = sliders ++ 16.collect{ |it|
				DMXSlider.new( dmx, gv, (37*it)+2 ).channel_( it + (16*it2) ).value_(0);
			};
			tabpane.setTitleAt( it2, (""++(it2*16)++"-"++((it2+1)*16-1)) );
			gv;
		};

	/*		sliders = 16.collect{ |it|
			DMXSlider.new( dmx, sliderview, (37*it)+5 ).channel_( it ).value_(0);
		};*/

		w.refresh;

		w.front;

		watcher = SkipJack.new( { this.updateVals }, 0.1, { w.isClosed }, (\dmx_console_ ++ counter));
		watcher.start;

	}

	updateVals { 
		{ 
			// update only the currently visible slider values
			sliders.copyRange( tabpane.value * 16, (tabpane.value+1)*16-1 ).do{ |it,i|
				it.value = dmx.currentCue.data.at( it.chan.value );
				}
		}.defer;
	}

	hide{
		if ( GUI.scheme.id == \swing,
			{
				w.visible_( false );
			},{
				w.close;
			});
		watcher.stop;
	}

	show{
		if ( GUI.scheme.id == \swing,
			{
				w.visible_( true );
			});
		watcher.start;
	}
}

DMXSlider{
	var <slider, <val, <chan, <nam;
	var <view;
	var <>dmx;

	*new{ |dmx,parent,xoff|
		^super.new.dmx_(dmx).init( parent, xoff );
	}

	init{ |parent,xoffset|
		var coff;
		coff = xoffset; // currently as compositeView is not relative
		view = GUI.compositeView.new( parent, Rect( xoffset, 0, 35, 150 ));
		slider = GUI.slider.new( view, Rect( coff, 0, 35, 80 ) ).action_({ |slid|
			val.valueAction_( slid.value );
		});
		val = GUI.numberBox.new( view, Rect( coff, 82, 35, 20 ) ).action_({ |nb|
			var curVal;
			curVal = nb.value.clip(0,1);
			dmx.currentCue.data.put( chan.value, curVal );
			if ( dmx.autoSet, { dmx.setCue } );
			val.value = curVal;
			slider.value = curVal;
			//slider.value = nb.value;
		}).step_(1/256);
		chan = GUI.numberBox.new( view, Rect( coff, 104, 35, 20 ) );
		nam = GUI.textField.new( view, Rect( coff, 126, 35, 20 ) ).action_({ |tf|
			dmx.map.put( tf.value.asSymbol, chan.value.asInteger );
		});
	}

	channel_{ |cval|
		chan.value = cval;
		nam.value = dmx.map.findKeyForValue( cval );
	}

	value_{ |cval|
		val.value = cval;
		slider.value = cval;
	}
}
// This contains a set of higher level classes
// Classes for a specific light, or a group of lights, or a complete set

DMXSet{
	var <>name;
	var <groups;

	*new{ |name|
		^super.new.init.name_( name );
	}

	init{ 
		groups = ();
	}

	addGroup{ |group|
		groups.put( group.name, group );
	}

	removeGroup{ |group|
		groups.removeAt( group.name );
	}
}

DMXGroup{
	var <>name;
	var <lights;

	*new{ |name|
		^super.new.init.name_( name );
	}

	init{ 
		lights = ();
	}

	addLight{ |light|
		lights.put( light.name, light );
	}

	removeLight{ |light|
		lights.removeAt( light.name );
	}
}

DMXLight{
	var <>name;
	var <properties;
	var <channels;

	*new{ |name|
		^super.new.init.name_( name );
	}

	init{ 
		properties = ();
		channels = ();
	}

	setStrobe{ |chans,vals|
		vals = vals ? [0,0,0];
		properties.put( \duration, vals[0] );
		properties.put( \intensity, vals[1] );
		properties.put( \rate, vals[2] );
		channels.put( \duration, chans[0] );
		channels.put( \intensity, chans[1] );
		channels.put( \rate, chans[2] );
		DMX.map.put( name++\_++\duration, chans[0] );
		DMX.map.put( name++\_++\intensity, chans[1] );
		DMX.map.put( name++\_++\rate, chans[2] );
	}

	setGob{ |chans,vals|
		/* set to useful stuff here
		vals = vals ? [0,0,0];
		properties.put( \duration, vals[0] );
		properties.put( \intensity, vals[1] );
		properties.put( \rate, vals[2] );
		channels.put( \duration, chans[0] );
		channels.put( \intensity, chans[1] );
		channels.put( \rate, chans[2] );
			DMX.map.put( name++\_++\duration, chans[0] );
		DMX.map.put( name++\_++\intensity, chans[1] );
		DMX.map.put( name++\_++\rate, chans[2] );
		*/
	}

	setLight{ |chans,vals|
		vals = vals ? [0,0];
		properties.put( \intensity, vals[0] );
		properties.put( \color, vals[1] );
		channels.put( \intensity, chans[0] );
		channels.put( \color, chans[1] );
		DMX.map.put( name++\_++\intensity, chans[0] );
		DMX.map.put( name++\_++\color, chans[1] );
	}

	set{ |key,val|
		properties.put( key, val );
	}
}

// with DMXColor you can store colormaps corresponding to the needed DMX value to get the specified color
DMXColor{
	classvar <>maps;
	classvar <>currentMap;

	*initClass{
		maps = ();
		currentMap = ();
	}

	*get{ |color|
		^currentMap.at( color );
	}
}
// DMX is a masterclass which contains the DMXCues, and the DMXDevice
// for now, it is assumed that there is always 1 dmx device
DMX{
	classvar <>device;

	classvar <>channeloffset=0;
	classvar <>maxchannels=512;
	classvar <>autoSet = false;

	// map is an IdentityDictionary, which allows you to use names for channels
	classvar <map;
	// cues are all the cues that are defined for this thing
	classvar <cues;
	// the current light cue, containing the settings for this moment
	classvar <>currentCue;

	classvar <fadeval;

	/*	*new{
		^super.new.init;
		}*/
	
	*initClass{
		map = ();
		cues = Array.new;
	}

	*setCurrentMap{ arg name;
		map = DMXMap.at( name );
	}

	*setCue{
		device.sendDMX( currentCue );
	}

	*blackout{ |time,curve|
		if ( time.isNil, {
			currentCue = DMXCue.new;
			this.setCue;
		},{
			this.fade( DMXCue.new, time, curve );
		});
	}

	*fade{ arg to, time=1.0, curve=\linear, timestep=0.025;
		var spec, startCue, endCue, nsteps, ddmx, curdmx;
		spec = [0,1,curve].asSpec;
		startCue = currentCue;
		if ( to.isKindOf( DMXSubCue ), { 
			endCue = currentCue.deepCopy.merge( to );
		}, {
			endCue = to;
		});
		endCue.data.postln;
		nsteps = round(time/timestep);
		// can't do more than 256 steps, due to 8bit resolution
		if ( nsteps > 256, { nsteps = 256; timestep = time/nsteps; } );
		ddmx = 1/nsteps;
		//		Tdef( \dmxfade ).envir = ();
		Tdef( \dmxfade, {
			//	envir = ();
			//			envir.put( \timestep, timestep );
			//			envir.put( \nsteps, nsteps );
			Tdef(\dmxfade).set( \speed, 1 );
			nsteps.do{ |i|
				currentCue = DMXCue.new;
				//	spec.map( 1-(ddmx*(i+1)) ).postln;
				currentCue.data = (startCue.data * spec.map( 1-(ddmx*(i+1)) ) ) + (endCue.data * spec.map( ddmx*(i+1) ) );
				fadeval = ddmx*(i+1); // could be displayed
				Tdef(\dmxfade).set( \fadeval, fadeval );
				//	currentCue.data.postln;
				this.setCue;
				( timestep / Tdef(\dmxfade).envir.speed ).wait;
			};
		});
		Tdef( \dmxfade ).play;
	}
}

DMXMap{
	classvar maps;
	
	*initClass{
		maps = ();
	}

	*addMap{ arg name;
		maps.put( name, () );
	}
	
	*at{ arg name;
		^maps.at( name );
	}

	*putItem{ arg name, itemName, value;
		maps.at( name ).put( itemName, value );
		^maps.at( name );
	}
}

// DMXCue is a complete set of DMX messages, which make up one scene.
DMXCue{
	var <>name;
	var <>id;

	var <>channeloffset=0;
	var <>maxchannels=512;
	var <>data;
	classvar spec;

	*initClass{
		ControlSpec.initClass;
		spec = [0, 256, \linear, 1].asSpec;
	}

	*new{ arg offset, maxch;
		^super.new.init( offset, maxch );
	}

	init{ arg offset, maxch;
		channeloffset = offset ? channeloffset;
		maxchannels = maxch ? maxchannels;
		data = Array.fill( this.size, 0 );
	}

	size{
		^(maxchannels-channeloffset);
	}

	// returns the cue as an Int8Array, for sending it to the device
	asInt8{
		^spec.map( data ).as( Int8Array );
	}

	merge{ |subcue|
		subcue.data.do{ |it,i|
			data.put( i, it );
		}
	}

	put{ |id, val|
		data.put( id, val );
	}

	at{ |id|
		^data.at( id );
	}
}

DMXSubCue{
	var <data;

	*new{
		^super.new.init;
	}

	init{ 
		data = Order.new;
	}

	put{ |id, val|
		data.put( id, val );
	}

	at{ |id|
		^data.at( id );
	}
}
// for now, we assume that the DMXDevice shows up as a SerialPort in the computer; this is the case for the EntTec DMX USB Pro, which is a subclass of DMXDevice
// DMXDevice just encapsulates the common properties of DMXDevices

DMXDevice : SerialPort{
	
	sendDMX{ arg cue;
		var datablob;
		var cuesize = cue.size + 1;
		// Int8Array[0] is the DMX start code
		datablob = this.createSendHeader(cuesize) ++ Int8Array[0] ++ cue.asInt8 ++ this.createFooter;
		// add in when testing for real:
		//this.putAll( datablob );
	}

	createSendHeader{ arg data_size=512;
		// subclass responsibility
		^Int8Array[];
	}

	createFooter{
		// subclass responsibility
		^Int8Array[];
	}
}
EntTecDMXUSBPro : DMXDevice {

	*new {
		| port,
		  baudrate(57600),
		  databits(8),
		  stopbit(true),
		  parity(nil),
		  crtscts(false),
		  xonxoff(false)
		  exclusive(false) |
		^super.new( port, baudrate, databits, stopbit, parity, crtscts, xonxoff, exclusive ).init;
	}

	init{
		DMX.device = this;
	}

	createSendHeader{ arg data_size = 512;
		// header consists of: 0x7E, label (in this case 6), datasize low byte, datasize high byte;
		//	ser.write(chr(data_size & 0xFF))
		//	ser.write(chr((data_size >> 8) & 0xFF))
		^Int8Array[ 0x7E, 6, data_size.bitAnd( 0xFF ), (data_size >> 8).bitAnd( 0xFF ) ];
	}

	createFooter{
		^Int8Array[ 0xE7 ];
	}
}