|
| 1 | +// VIS Interval Sonifier (or VIS) |
| 2 | +// |
| 3 | +// |
| 4 | +// Created by R. Michael Winters |
| 5 | +// Input Devices and Music Interaction Laboratory |
| 6 | +// September 10, 2013 |
| 7 | +// |
| 8 | +// Running SuperCollider 3.6.5 MacOSX 10.7.5 |
| 9 | +// Double click inside parenthesis to select all |
| 10 | +// Shift-Enter to Evaluate |
| 11 | +// Command-D for Help |
| 12 | + |
| 13 | +s.boot; |
| 14 | + |
| 15 | +( |
| 16 | +// GUI Object Variables |
| 17 | +var loadB, clearB, titleS, loadV, chk1, chk2, chk3, chk1str ="", chk2str ="", |
| 18 | +chk3str="", knb1, knb2, knb3, knb1str="", knb2str="", knb3str ="", datastr="", |
| 19 | +horpos, vertpos, playindx, playB, sizestr, dataV, |
| 20 | +vertMaxStr, vertMinStr, horzMaxStr, horzMinStr, horzChk, vertChk, |
| 21 | +dataFont, |
| 22 | +xMinStr, xMaxStr; // |
| 23 | + |
| 24 | +// Making your own error dialog |
| 25 | +var errorD, errorT, errorS=""; |
| 26 | + |
| 27 | +// Synth Controls: |
| 28 | +var transposition, spread, speed, len, task, task2, timepos, whichdata; |
| 29 | + |
| 30 | +//Backend Variables |
| 31 | +var paths=[], filenames=[], data; |
| 32 | + |
| 33 | +// Color Palette: |
| 34 | +//QtGUI.palette= QPalette.auto(Color.cyan(1.4), Color.cyan(1.8)); |
| 35 | + QtGUI.palette =QPalette.dark; |
| 36 | + |
| 37 | +// Basic GUI Set-up |
| 38 | +w = Window.new("VIS Interval Sonifier", Rect(Window.availableBounds.width/2-300,Window.availableBounds.height/2-250,600,500)).front; |
| 39 | +w.layout_(GridLayout.rows( |
| 40 | +/*row0*/ [nil,nil,nil,nil,nil,nil,[titleS=StaticText(), rows:2, columns:6]], |
| 41 | +/*row1*/ [[loadB=Button(),columns:2],nil,[clearB=Button(),columns:2]], |
| 42 | +/*row2*/ [[loadV=ListView(),columns:6,rows:4]], |
| 43 | +/*row3*/ [nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil], |
| 44 | +/*row4*/ [nil,nil,nil,nil,nil,nil, |
| 45 | + [knb1str=StaticText(),columns:2],nil,[knb2str=StaticText(),columns:2],nil, |
| 46 | + [knb3str=StaticText(),columns:2]], |
| 47 | +/*row5*/ [nil,nil,nil,nil,nil,nil,[knb1=Knob(),columns:2],nil, |
| 48 | + [knb2=Knob(),columns:2],nil,[knb3=Knob(),columns:2],nil], |
| 49 | +/*row6*/ [nil,[playB=Button(),columns:2],nil, |
| 50 | + [sizestr=StaticText(),columns:2],nil,nil, |
| 51 | + [playindx=StaticText(),columns:2],nil, |
| 52 | + [vertpos=StaticText(),columns:2],nil,[horpos=StaticText(),columns:2],nil], |
| 53 | +/*row7*/[vertMaxStr = StaticText(), [dataV=SoundFileView(),columns:11, rows:6]], |
| 54 | +/*row8*/ [vertChk = CheckBox()], |
| 55 | +/*row9*/ [vertMinStr = StaticText()], |
| 56 | +/*row10*/ [horzMaxStr = StaticText()], |
| 57 | +/*row11*/ [horzChk = CheckBox()], |
| 58 | +/*row12*/ [horzMinStr = StaticText()], |
| 59 | +/*row13*/ [nil,[xMinStr=StaticText()],nil,nil,nil,nil,nil,nil,nil,nil,nil,[xMaxStr = StaticText()]] |
| 60 | +)); |
| 61 | + |
| 62 | +/*// Must put stuff on top of other stuff for this view |
| 63 | +w.layout.add(vertMaxStr=StaticText(),7,0); |
| 64 | +w.layout.add(xMinStr=StaticText(),12,0); |
| 65 | +w.layout.add(xMaxStr=StaticText(),12,5);*/ |
| 66 | + |
| 67 | + |
| 68 | +// On close do what? |
| 69 | +w.onClose_({task.stop;task2.stop;}); |
| 70 | + |
| 71 | +// Got to make a warning dialog from scratch. |
| 72 | +errorD=Window.new("ERROR",Rect(Window.availableBounds.width/2-150, Window.availableBounds.height/2-45,300,90)); |
| 73 | +errorD.layout_(GridLayout.rows([[errorT=StaticText()]])); |
| 74 | +errorD.layout.setAlignment(errorT, \center); |
| 75 | +errorT.string_("File Must be a .csv!"); |
| 76 | +errorT.font = Font("Monaco", 15); |
| 77 | + |
| 78 | + |
| 79 | +// Center things |
| 80 | +w.layout.setAlignment(titleS, \center); |
| 81 | +w.layout.setAlignment(knb1str, \bottom); |
| 82 | +w.layout.setAlignment(knb2str, \bottom); |
| 83 | +w.layout.setAlignment(knb3str, \bottom); |
| 84 | +w.layout.setAlignment(vertMaxStr, \topRight); |
| 85 | +w.layout.setAlignment(vertMinStr, \bottomRight); |
| 86 | +w.layout.setAlignment(horzMaxStr, \topRight); |
| 87 | +w.layout.setAlignment(horzMinStr, \bottomRight); |
| 88 | +w.layout.setAlignment(xMaxStr,\bottomRight); |
| 89 | +w.layout.setAlignment(xMinStr,\bottomLeft); |
| 90 | +w.layout.setAlignment(vertChk,\right); |
| 91 | +w.layout.setAlignment(horzChk,\right); |
| 92 | + |
| 93 | + |
| 94 | +// Set Text Strings |
| 95 | +titleS.string_("VIS Interval Sonifer"); |
| 96 | +knb1str.string_("Spread"); |
| 97 | +knb2str.string_("Pitch"); |
| 98 | +knb3str.string_("Speed"); |
| 99 | +/*chk1str.string_("Vertical"); |
| 100 | +chk2str.string_("Horizontal"); |
| 101 | +chk3str.string_("Normalize");*/ |
| 102 | +loadB.states_([["Load"]]); |
| 103 | +clearB.states_([["Clear"]]); |
| 104 | +playB.states_([["Play"],["Pause"]]); |
| 105 | +vertpos.string_("Vert: 0"); |
| 106 | +horpos.string_("Horz: 0"); |
| 107 | +playindx.string_("Index: 0"); |
| 108 | +sizestr.string_("Size"); |
| 109 | +vertMaxStr.string_("Max"); |
| 110 | +vertMinStr.string_("Min"); |
| 111 | +horzMaxStr.string_("Max"); |
| 112 | +horzMinStr.string_("Min"); |
| 113 | +vertChk.string_("Vert"); |
| 114 | +horzChk.string_("Horz"); |
| 115 | +xMinStr.string_("Start"); |
| 116 | +xMaxStr.string_("End"); |
| 117 | + |
| 118 | + |
| 119 | +// What font to use in the data plot? |
| 120 | +dataFont = Font("Helvetica",10); |
| 121 | +vertMaxStr.font_(dataFont); |
| 122 | +vertMinStr.font_(dataFont); |
| 123 | +horzMaxStr.font_(dataFont); |
| 124 | +horzMinStr.font_(dataFont); |
| 125 | +xMinStr.font_(dataFont); |
| 126 | +xMaxStr.font_(dataFont); |
| 127 | + |
| 128 | +// Format the Title |
| 129 | +titleS.font = Font("Monaco", 25); |
| 130 | + |
| 131 | +// Loading Files |
| 132 | +loadB.action_({Dialog.getPaths({|path| |
| 133 | + // For each selected path... |
| 134 | + path.size.do({|i| var test; |
| 135 | + // If the path ends with .csv, then take it, otherwise send error message. |
| 136 | + test = PathName.new(path[i]); |
| 137 | + if(test.extension == "csv", |
| 138 | + //... declate it as a path and add it to the list of paths |
| 139 | + {paths=paths++test},{errorD.front}); |
| 140 | + //... declate it as a path and add it to the list of paths |
| 141 | + }); |
| 142 | + // Add the filenames to the list of filenames... |
| 143 | + filenames=paths.collect({|i| i.fileName}); |
| 144 | + // ... and set those names as the list. |
| 145 | + loadV.items_(filenames.reverse); |
| 146 | +/* loadV.items[loadV.value].postln; |
| 147 | + loadV.valueAction_(loadV.value.postln); |
| 148 | + filenames.reverse[loadV.value].postln;*/ |
| 149 | + }, |
| 150 | + {"cancelled".postln}) |
| 151 | +}); |
| 152 | + |
| 153 | +// What does the clear button do? |
| 154 | +clearB.action_({loadV.clear; filenames=[]; paths=[]}); |
| 155 | + |
| 156 | +// // What happens when the user selects different data? |
| 157 | + loadV.selectionAction_({var loadthis; |
| 158 | + //Get full path name. |
| 159 | + loadthis=paths.reverse[loadV.value].fullPath; |
| 160 | + // Read and interpret the elements as floats. |
| 161 | + data=CSVFileReader.readInterpret(loadthis).postln; |
| 162 | + // Set the data to two channels (vertical, horizontal). You must interleave. |
| 163 | + dataV.setData((data.flop[0].normalize(-1,1)++data.flop[1].normalize(-1,1)).perfectShuffle ,channels:2); |
| 164 | + // Set the strings for more visual feedback. |
| 165 | + vertpos.string_("Vert:"+data.flop[0][dataV.timeCursorPosition].asString); |
| 166 | + horpos.string_("Horz:"+data.flop[1][dataV.timeCursorPosition].asString); |
| 167 | + playindx.string_("Index:"+dataV.timeCursorPosition.asString); |
| 168 | + sizestr.string_("Size: "+data.flop[0].size.asString); |
| 169 | + }); |
| 170 | + |
| 171 | + |
| 172 | +// What is the fundamental synth? |
| 173 | +SynthDef(\newSynth, { |
| 174 | + arg basefreq=200, pos=0.5, gain=0.5, dur=0.05; |
| 175 | + var sound, env, synth, stereo; |
| 176 | + sound=gain*SinOsc.ar(basefreq, 0, 1)*AmpComp.kr(basefreq,40.midicps); |
| 177 | + env=EnvGen.kr(Env.sine(dur,1), timeScale:1, doneAction:2); |
| 178 | + synth=sound*env; |
| 179 | + stereo=Pan2.ar(synth, pos, gain); |
| 180 | + OffsetOut.ar(0,stereo); |
| 181 | +}).send(s); |
| 182 | + |
| 183 | +// Define the look of the SoundFileView |
| 184 | +dataV.timeCursorOn = true; |
| 185 | +dataV.timeCursorColor = Color.red; |
| 186 | +dataV.gridOn = true; |
| 187 | +dataV.gridResolution = 0.05; |
| 188 | +dataV.action_({ var indx = dataV.timeCursorPosition; |
| 189 | + vertpos.string_("Vert:"+data.flop[0][indx].asString); |
| 190 | + horpos.string_("Horz:"+data.flop[1][indx].asString); |
| 191 | + playindx.string_("Index:"+indx.asString); |
| 192 | + dataV.timeCursorPosition=dataV.selection(0)[0]; |
| 193 | + timepos=indx;}); |
| 194 | + |
| 195 | +// Set synth controls: |
| 196 | +transposition=72; |
| 197 | +spread=1; |
| 198 | +speed=0.01; |
| 199 | +timepos=0; |
| 200 | + |
| 201 | +// The Sonification task; |
| 202 | +task=Task({ |
| 203 | + inf.do({arg i; |
| 204 | + //[data[timepos][0], i].postln; |
| 205 | + Synth.new(\newSynth, [\basefreq,((data[(timepos)][whichdata]*spread)+transposition).midicps]); |
| 206 | + timepos = timepos+1; |
| 207 | + //timepos.postln; |
| 208 | + (speed).wait; |
| 209 | +}) |
| 210 | + }); |
| 211 | + |
| 212 | +// I couldn't get this to schedule right, so I'm doing it here. |
| 213 | +task2 = Task({ |
| 214 | + inf.do({arg j; |
| 215 | + {dataV.timeCursorPosition = timepos; |
| 216 | + vertpos.string_("Vert:"+data.flop[0][dataV.timeCursorPosition].asString); |
| 217 | + horpos.string_("Horz:"+data.flop[1][dataV.timeCursorPosition].asString); |
| 218 | + playindx.string_("Index:"+dataV.timeCursorPosition;);}.defer(0); |
| 219 | + (1/25).wait; |
| 220 | + }); |
| 221 | +}); |
| 222 | + |
| 223 | +// The play button |
| 224 | +playB.action_({arg butt; |
| 225 | + // If there is no data, it becomes a tester. |
| 226 | + if(paths.isEmpty,{ |
| 227 | + Synth.new(\newSynth, [\basefreq, 60.midicps, \pos, 1.0.rand2, \dur, 1, \gain, 0.3])}, |
| 228 | + {// Assuming it is not empty, then create a task: |
| 229 | + timepos = dataV.selection(0)[0]; |
| 230 | + //len = if(dataV.selection(0)[1]==0,{data.flop[0].size-timepos},{dataV.selection(0)[1]}); |
| 231 | + if(butt.value==1,{task.play; task2.play;},{task.stop; task2.stop;}); |
| 232 | + }) |
| 233 | + }); |
| 234 | + |
| 235 | +// Center the knobs strings |
| 236 | +knb1str.align = \center; |
| 237 | +knb2str.align = \center; |
| 238 | +knb3str.align = \center; |
| 239 | + |
| 240 | +// Knobs: |
| 241 | +knb1.value_(0.5); |
| 242 | +knb2.value_(0.5); |
| 243 | +knb3.value_(0.5); |
| 244 | + |
| 245 | +// What do the knobs do? |
| 246 | +knb1.action_({var val=(knb1.value*2).round(0.1); |
| 247 | + knb1str.string_("Spread: "+val.asString+"\n(Octaves)"); |
| 248 | + spread=val;}); |
| 249 | +knb2.action_({var val=((knb2.value*48)+48).round; |
| 250 | + knb2str.string_("Pitch: "+val.asString+"\n(MIDI)"); |
| 251 | +transposition=val;}); |
| 252 | +knb3.action_({var val=(10.pow((knb3.value*2+1))).round; |
| 253 | + knb3str.string_("Speed: "+val.asString+"\n(Notes/Sec)"); |
| 254 | + speed=1/val}); |
| 255 | + |
| 256 | +// Set check boxes: |
| 257 | +vertChk.value_(true); // default; |
| 258 | +horzChk.value_(false); // default; |
| 259 | +vertChk.action_({if(vertChk.value,{horzChk.value_(0);whichdata=0},{vertChk.value_(1)})}); |
| 260 | +horzChk.action_({if(horzChk.value,{vertChk.value_(0);whichdata=1},{horzChk.value_(1)})}); |
| 261 | + |
| 262 | +// Which of the two columns is being evaluated by default? |
| 263 | +whichdata=0; |
| 264 | + |
| 265 | +) |
| 266 | + |
| 267 | + |
| 268 | + |
| 269 | + |
| 270 | +//Trash: |
0 commit comments