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

Re: [sc-users] NanoKontrol.sc



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.

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; 

	*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, 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 { |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), 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 + pxOffset].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, 
		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: (
				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: (
				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: (
				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: (
				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'				
			);
	}
}