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

Re: [sc-users] NanoKontrol.sc



On Wednesday 29 July 2009 22:34:17 nescivi wrote:
> On Wednesday 29 July 2009 21:57:46 nescivi wrote:
> > On Wednesday 29 July 2009 21:19:43 nescivi wrote:
> > > On Wednesday 29 July 2009 20:51:40 nescivi wrote:
> > > > On Wednesday 29 July 2009 14:49:43 Alberto de Campo wrote:
> > > > > hi,
> > > > >
> > > > > I've made a simple NanoKtl class, mainly for connecting to
> > > > > NodeProxies, but also general. I did not think of conforming to
> > > > > JInt, but could be worth converting and adding.
> > > >
> > > > Oooh, these are really nice!
> > >
> > > and a small bugfix for the map to proxy mixer...
> > > the start/stop buttons were not shifted along with the shift function.
> >
> > One question though...
> > It seems that with fast movements of the sliders or knobs, the controls
> > don't catch up. With verbose on, the control messages show up fine
> > though, so the problem is somewhere further down the line...
> > Hmm... this seems to be a consequence of softSet... maybe we should
> > remember the last vals, in case we don't shift parameters?
>
> A simple fix for this: I added a parameter "softWithin" to the class, so
> you can set the behaviour to your own taste...
>
> Another note: with the factory settings scene 4, the buttons are in toggle
> mode, i.e. first time push they send out 127, second time 0 (and no release
> value). Nice aspect is that the light under the button turns on, so you can
> see that it's on...
> But, this does need some other behaviour in the mapping to work properly...
> (who knows, maybe in half an hour?)

And yes...
each scene has now a mode, which is 'push' in scene 1,2,3 and 'toggle' in 
scene 4, and the nodeproxy start/stop function is different accordingly.
Can't make this shift along unfortunately, as the controller doesn't seem to 
be able to support receiving control changes to change the state of the 
buttons, so you'll have to do some extra pushing of buttons then.

sincerely,
Marije
MIDIController { 
	var <srcID, <ccDict, <noteOnDict, <noteOffDict, <ccresp;

	*new { |srcID, ccDict| 
		^super.newCopyArgs(srcID, ccDict).init;
	}
	
	free { 
		ccresp.remove;
		
		ccDict.clear;
		noteOnDict.clear;
		noteOffDict.clear;
		// redraw pxmix and pxedit with clear colors...
	}

	init { 
		ccDict = ccDict ?? ();
		noteOnDict = noteOnDict ?? ();
		noteOffDict = noteOffDict ?? ();
		
		ccresp.remove; 
		ccresp = CCResponder({ |src, chan, ccn, val| 
			var lookie = this.makeCCKey(chan, ccn);
			if (this.class.verbose, { ['cc', src, chan, ccn, val].postcs });
			
			ccDict[lookie].value(chan, ccn, val);
		}, srcID);
	}

	makeCCKey { |chan, cc| ^(chan.asString ++ "_" ++ cc).asSymbol }
	
	ccKeyToChanCtl { |ccKey| ^ccKey.asString.split($_).asInteger }

	makeNoteKey { |chan, note| 
		var key = chan.asString; 
		if (note.notNil) { key = key ++ "_" ++ note };
		^key.asSymbol 
	}
}

/*	// scene 1: all on chan 0 
#	1	2	3	4	5	6	7	8	9
\kn	(14	               ..               22)
\sl	2, 	3, 	4, 	5, 	6, 	8, 	9, 	12, 13
\bu	(23                ..                31)
\bd	(33               ..                 41)
*/


NanoKtl : MIDIController { 
	classvar <>verbose = false, <defaultSceneNames; 
	var <pxmixers, <pxEditors, <pxOffset = 0, <parOffset = 0;
	var <>softWithin = 0.05;

	*initClass {
		this.makeDefaults;
	}
	
	init { 
		super.init; 
		pxmixers = ();
		pxEditors = ();
		^this
	}

	mapCC { |scene=2, ctl= \sl1, action| 
		var ccDictKey = defaultSceneNames[scene][ctl]; // '0_42'
		ccDict.put(ccDictKey, action);
	}
		
	mapToPxEdit { |editor, scene=1| 
		pxEditors.put(scene, editor);
		
			// map 8 knobs to params - can be shifted
		 [\kn1, \kn2, \kn3, \kn4, \kn5, \kn6, \kn7, \kn8].do { |key, i| 
			this.mapCC(scene, key, 
				{ |ch, cc, val| 
					var parKey =  pxEditors[scene].editKeys[i + parOffset];
					if (parKey.notNil) { pxEditors[scene].proxy.softSet(parKey, val / 127, softWithin ) };
				}
			)
		};
			// and use 9th knob for proxy volume 
		this.mapCC(scene, \kn9, { |ch, cc, val| 
			pxEditors[scene].proxy.softVol_(\amp.asSpec.map(val / 127), softWithin) 
		} );
	}
	
	mapToPxPars { |scene = 2, proxy ... pairs| 
		pairs.do { |pair| 
			var ctlName, paramName; 
			#ctlName, paramName = pair;
			this.mapCC(scene, ctlName, 
				{  |ch, cc, midival| 
					proxy.set(paramName, paramName.asSpec.map(midival / 127))
				}
			);
		};
	}
	
		// convenience method to map to a proxymixer 
		// could and should be refactored for more general use! 
	mapToPxMix { |mixer, scene = 1| 
	
		var server, mastaFunc; 
		pxmixers.put(scene, mixer); 
		server = mixer.proxyspace.server;

			// add master volume to all 4 scenes, on slider 9: 
		mastaFunc = { |chan, cc, val| server.volume.volume_(\mastaVol.asSpec.map(val/127)) };
			Spec.add(\mastaVol, [server.volume.min, server.volume.max, \db]);
			(1..4).do { |scene| this.mapCC(scene, \sl9, mastaFunc) };
			
			// scene 1: 

			// map first 8 volumes to sliders
		[\sl1, \sl2, \sl3, \sl4, \sl5, \sl6, \sl7, \sl8].do { |key, i| 
			this.mapCC(scene, key, 
				{ |ch, cc, val| 
					try { pxmixers[scene].pxMons[i + pxOffset].proxy
						.softVol_( \amp.asSpec.map(val / 127), softWithin ) 
					} 
				};
			)
		};
			// upper buttons: send to editor
		[\bu1, \bu2, \bu3, \bu4, \bu5, \bu6, \bu7, \bu8].do { |key, i| 
			this.mapCC(scene, key, 
				{ |ch, cc, val| defer { pxmixers[scene].editBtnsAr[i + pxOffset].doAction }; })
		};
		
			// lower buttons: toggle play/stop 
		 [\bd1, \bd2, \bd3, \bd4, \bd5, \bd6, \bd7, \bd8].do { |key, i| 
			this.mapCC(scene, key, 
				{ |ch, cc, val| defer { 
					var px = pxmixers[scene].pxMons[i + pxOffset].proxy;
					if ( defaultSceneNames[scene]['mode'] == 'push' ){
						if (val == 127) { 
							try { if (px.monitor.isPlaying) { px.stop } { px.play } };
						};
					};
					if ( defaultSceneNames[scene]['mode'] == 'toggle' ){
						if (val == 127) { px.play };
						if (val == 0) { px.stop };
					};
					};
				}; )
		};

		this.mapCC(scene, \bu9, { |src, chan, val| if (val > 0) { this.pxShift(1, scene) } });
		this.mapCC(scene, \bd9, { |src, chan, val| if (val > 0) { this.paramShift(1, scene) } });

		this.pxShift(0, scene);		
		this.mapToPxEdit(mixer.editor, scene);
		this.paramShift(0, scene);
	}
		
		// proxymixer shifting support: 

	pxShift { |step = 1, scene=1| 
		{	 
			var onCol = Color(1, 0.5, 0.5);
			var offCol = Color.clear;
			var numActive = pxmixers[scene].pxMons.count { |mon| mon.zone.visible == true };
			var maxOff = (numActive - 8).max(0);
			pxOffset = pxOffset + step; 
			pxOffset = pxOffset.wrap(0, maxOff);
			
			[ \pxOffset, pxOffset].postcs; 
			
			pxmixers[scene].pxMons.do { |mong, i| 
				var col = if (i >= pxOffset and: (i < (pxOffset + 8).max(0)), onCol, offCol); 
				mong.nameView.background_(col);
				// write indices there as well
			} 
		}.defer;
	}
	
	paramShift { |step = 1, scene=1| 
		{
		var onCol = Color(1, 0.5, 0.5);
		var offCol = Color.clear;
		var numActive = pxEditors[scene].edits.count { |edi| edi.visible == true };
		var maxOff = (numActive - 8).max(0);
		parOffset = parOffset + step; 
		parOffset = parOffset.wrap(0, maxOff);
		
		[ \parOffset, parOffset].postcs; 
		
		pxEditors[scene].edits.do { |edi, i| 
			var col = if (i >= parOffset and: (i < (parOffset + 8).max(0)), onCol, offCol); 
			edi.labelView.background_(col);
		} }.defer;
	}
	
	*makeDefaults { 

		// lookup for all scenes and ctlNames, \sl1, \kn1, \bu1, \bd1, 
		defaultSceneNames = (
				// general controls that do not change with scenes:
			0: (
				rew: '0_47',
				fwd: '0_48',
				play: '0_45',
				loop: '0_49',
				stop: '0_46',
				rec: '0_44'
			),
			
			1: (
				mode: 'push',
				sl1: '0_2', 
				sl2: '0_3', 
				sl3: '0_4', 
				sl4: '0_5', 
				sl5: '0_6', 
				sl6: '0_8', 
				sl7: '0_9', 
				sl8: '0_12', 
				sl9: '0_13',

				kn1: '0_14', 
				kn2: '0_15', 
				kn3: '0_16', 
				kn4: '0_17', 
				kn5: '0_18', 
				kn6: '0_19', 
				kn7: '0_20', 
				kn8: '0_21', 
				kn9: '0_22',

				bu1: '0_23', 
				bu2: '0_24', 
				bu3: '0_25', 
				bu4: '0_26', 
				bu5: '0_27', 
				bu6: '0_28', 
				bu7: '0_29', 
				bu8: '0_30', 
				bu9: '0_31',

				bd1: '0_33', 
				bd2: '0_34', 
				bd3: '0_35', 
				bd4: '0_36', 
				bd5: '0_37', 
				bd6: '0_38', 
				bd7: '0_39', 
				bd8: '0_40', 
				bd9: '0_41'
			), 
			
			
			2: (
				mode: 'push',
				sl1: '0_42', 
				sl2: '0_43', 
				sl3: '0_50', 
				sl4: '0_51', 
				sl5: '0_52', 
				sl6: '0_53', 
				sl7: '0_54', 
				sl8: '0_55', 
				sl9: '0_56',

				kn1: '0_57', 
				kn2: '0_58', 
				kn3: '0_59', 
				kn4: '0_60', 
				kn5: '0_61', 
				kn6: '0_62', 
				kn7: '0_63', 
				kn8: '0_65', 
				kn9: '0_66',

				bu1: '0_67', 
				bu2: '0_68', 
				bu3: '0_69', 
				bu4: '0_70', 
				bu5: '0_71', 
				bu6: '0_72', 
				bu7: '0_73', 
				bu8: '0_74', 
				bu9: '0_75',

				bd1: '0_76', 
				bd2: '0_77', 
				bd3: '0_78', 
				bd4: '0_79', 
				bd5: '0_80', 
				bd6: '0_81', 
				bd7: '0_82', 
				bd8: '0_83', 
				bd9: '0_84'
			),
			
			3: (
				mode: 'push',
				sl1: '0_85', 
				sl2: '0_86', 
				sl3: '0_87', 
				sl4: '0_88', 
				sl5: '0_89', 
				sl6: '0_90', 
				sl7: '0_91', 
				sl8: '0_92', 
				sl9: '0_93',

				kn1: '0_94', 
				kn2: '0_95', 
				kn3: '0_96', 
				kn4: '0_97', 
				kn5: '0_102', 
				kn6: '0_103', 
				kn7: '0_104', 
				kn8: '0_105', 
				kn9: '0_106',

				bu1: '0_107', 
				bu2: '0_108', 
				bu3: '0_109', 
				bu4: '0_110', 
				bu5: '0_111', 
				bu6: '0_112', 
				bu7: '0_113', 
				bu8: '0_114', 
				bu9: '0_115',

				bd1: '0_116', 
				bd2: '0_117', 
				bd3: '0_118', 
				bd4: '0_119', 
				bd5: '0_120', 
				bd6: '0_121', 
				bd7: '0_122', 
				bd8: '0_123', 
				bd9: '0_124'
			),
			
			4: (
				mode: 'toggle',
				sl1: '0_7', 
				sl2: '1_7', 
				sl3: '2_7', 
				sl4: '3_7', 
				sl5: '4_7', 
				sl6: '5_7', 
				sl7: '6_7', 
				sl8: '7_7', 
				sl9: '8_7',

				kn1: '0_10', 
				kn2: '1_10', 
				kn3: '2_10', 
				kn4: '3_10', 
				kn5: '4_10', 
				kn6: '5_10', 
				kn7: '6_10', 
				kn8: '7_10', 
				kn9: '8_10',
							// buttons toggle!
				bu1: '0_16', 
				bu2: '1_16', 
				bu3: '2_16', 
				bu4: '3_16', 
				bu5: '4_16', 
				bu6: '5_16', 
				bu7: '6_16', 
				bu8: '7_16', 
				bu9: '8_16',

				bd1: '0_17', 
				bd2: '1_17', 
				bd3: '2_17', 
				bd4: '3_17', 
				bd5: '4_17', 
				bd6: '5_17', 
				bd7: '6_17', 
				bd8: '7_17', 
				bd9: '8_17'
			)
		);
	}
}

BCRKtl : MIDIController { 
	classvar <>verbose = false, <defaultCtlNames; 
	classvar <>midiOut;

	*makeMIDIOut { |index = 0| 
		midiOut = MIDIOut(index, MIDIClient.destinations[index].uid)
	}
	
	*initClass {
		this.makeDefaults;
	}
	
	init { 
		super.init; 
		^this
	}

	mapCC { |ctl= \sl1, action| 
		var ccDictKey = defaultCtlNames[ctl]; // '0_42'
		ccDict.put(ccDictKey, action);
	}
		
//	mapToPxEdit { |editor, scene=1| 
//		pxEditors.put(scene, editor);
//		
//			// map 8 knobs to params - can be shifted
//		 [\kn1, \kn2, \kn3, \kn4, \kn5, \kn6, \kn7, \kn8].do { |key, i| 
//			this.mapCC(scene, key, 
//				{ |ch, cc, val| 
//					var parKey =  pxEditors[scene].editKeys[i + parOffset];
//					if (parKey.notNil) { pxEditors[scene].proxy.softSet(parKey, val / 127, 0.05) };
//				}
//			)
//		};
//			// and use 9th knob for proxy volume 
//		this.mapCC(scene, \kn9, { |ch, cc, val| 
//			pxEditors[scene].proxy.softVol_(\amp.asSpec.map(val / 127), 0.05) 
//		} );
//	}
	
	mapToPxPars { |proxy ... pairs| 
		if (midiOut.notNil) { 
			this.sendFromProxy(proxy, pairs);
		};		
		
		pairs.do { |pair| 
			var ctlName, paramName; 
			#ctlName, paramName = pair;
			this.mapCC(ctlName, 
				{  |ch, cc, midival| 
					proxy.set(paramName, paramName.asSpec.map(midival / 127))
				}
			);
		};
	}
		
	sendFromProxy { |proxy, pairs| 
	
		var ctlNames, paramNames, currVals, midivals;
		#ctlNames, paramNames = pairs.flop;
		currVals = proxy.getKeysValues(paramNames).flop[1];
		midivals = currVals.collect { |currval, i|
			(paramNames[i].asSpec.unmap(currval) * 127).round.asInteger;
		};
		midivals.postln;
		
		[ctlNames, midivals].flop.do { |pair|
			this.sendCtlValue(*pair);
		}	
		
	}
	
	sendCtlValue { |ctlName, midival| 
		var chanCtl = this.ccKeyToChanCtl(defaultCtlNames[ctlName]);
		midiOut.control(chanCtl[0], chanCtl[1], midival);
	}
		
//		// convenience method to map to a proxymixer 
//		// could and should be refactored for more general use! 
//	mapToPxMix { |mixer, scene = 1| 
//	
//		var server, mastaFunc; 
//		pxmixers.put(scene, mixer); 
//		server = mixer.proxyspace.server;
//
//			// add master volume to all 4 scenes, on slider 9: 
//		mastaFunc = { |chan, cc, val| server.volume.volume_(\mastaVol.asSpec.map(val/127)) };
//			Spec.add(\mastaVol, [server.volume.min, server.volume.max, \db]);
//			(1..4).do { |scene| this.mapCC(scene, \sl9, mastaFunc) };
//			
//			// scene 1: 
//
//			// map first 8 volumes to sliders
//		[\sl1, \sl2, \sl3, \sl4, \sl5, \sl6, \sl7, \sl8].do { |key, i| 
//			this.mapCC(scene, key, 
//				{ |ch, cc, val| 
//					try { pxmixers[scene].pxMons[i + pxOffset].proxy
//						.softVol_( \amp.asSpec.map(val / 127), 0.05) 
//					} 
//				};
//			)
//		};
//			// upper buttons: send to editor
//		[\bu1, \bu2, \bu3, \bu4, \bu5, \bu6, \bu7, \bu8].do { |key, i| 
//			this.mapCC(scene, key, 
//				{ |ch, cc, val| defer { pxmixers[scene].editBtnsAr[i + pxOffset].doAction }; })
//		};
//		
//			// lower buttons: toggle play/stop 
//		 [\bd1, \bd2, \bd3, \bd4, \bd5, \bd6, \bd7, \bd8].do { |key, i| 
//			this.mapCC(scene, key, 
//				{ |ch, cc, val| defer { 
//					var px = pxmixers[scene].pxMons[i].proxy; 
//					if (val == 127) { 
//						try { if (px.monitor.isPlaying) { px.stop } { px.play } };
//					};
//				 }; })
//		};
//
//		this.mapCC(scene, \bu9, { |src, chan, val| if (val > 0) { this.pxShift(1, scene) } });
//		this.mapCC(scene, \bd9, { |src, chan, val| if (val > 0) { this.paramShift(1, scene) } });
//
//		this.pxShift(0, scene);		
//		this.mapToPxEdit(mixer.editor, scene);
//		this.paramShift(0, scene);
//	}
//		
//		// proxymixer shifting support: 
//
//	pxShift { |step = 1, scene=1| 
//		{	 
//			var onCol = Color(1, 0.5, 0.5);
//			var offCol = Color.clear;
//			var numActive = pxmixers[scene].pxMons.count { |mon| mon.zone.visible == true };
//			var maxOff = (numActive - 8).max(0);
//			pxOffset = pxOffset + step; 
//			pxOffset = pxOffset.wrap(0, maxOff);
//			
//			[ \pxOffset, pxOffset].postcs; 
//			
//			pxmixers[scene].pxMons.do { |mong, i| 
//				var col = if (i >= pxOffset and: (i < (pxOffset + 8).max(0)), onCol, offCol); 
//				mong.nameView.background_(col);
//				// write indices there as well
//			} 
//		}.defer;
//	}
//	
//	paramShift { |step = 1, scene=1| 
//		{
//		var onCol = Color(1, 0.5, 0.5);
//		var offCol = Color.clear;
//		var numActive = pxEditors[scene].edits.count { |edi| edi.visible == true };
//		var maxOff = (numActive - 8).max(0);
//		parOffset = parOffset + step; 
//		parOffset = parOffset.wrap(0, maxOff);
//		
//		[ \parOffset, parOffset].postcs; 
//		
//		pxEditors[scene].edits.do { |edi, i| 
//			var col = if (i >= parOffset and: (i < (parOffset + 8).max(0)), onCol, offCol); 
//			edi.labelView.background_(col);
//		} }.defer;
//	}
	
	*makeDefaults { 

		// lookup for all scenes and ctlNames, \sl1, \kn1, \bu1, \bd1, 
		defaultCtlNames = (
			
				knA1: '0_1', 
				knA2: '0_2', 
				knA3: '0_3', 
				knA4: '0_4', 
				knA5: '0_5', 
				knA6: '0_6', 
				knA7: '0_7', 
				knA8: '0_8', 

				knB1: '0_33', 
				knB2: '0_34', 
				knB3: '0_35', 
				knB4: '0_36', 
				knB5: '0_37', 
				knB6: '0_38', 
				knB7: '0_39', 
				knB8: '0_40', 
				
				// knC : 41-49
				// knD : 50-57
				
				btA1: '0_89',
				btA2: '0_90',
				btA3: '0_91',
				
				btB1: '0_97',
				btB2: '0_98',
				btB3: '0_99'				
			);
	}
}