/* Time seies hearing test Christopher Frauenberger [frauenberger@iem.at] 4 Sep 2006 */ ( // basic init var win, width, height, layout, info, conwin; q = q ? (); q.dir = Document.current.dir; // scrambled audio files q.scrFiles = (q.dir ++ "/scrambled/*.aiff").pathMatch; // control window conwin = SCWindow("Time series analysis experiment control", Rect(10, 650, 290, 60)).front; SCButton(conwin, Rect(10,0,80,20)).states_([["Training"]]).action = { |but| // Training q.etest.value; q.files = (q.dir ++ "/training/*.aiff").pathMatch; q.logFilename = q.dir ++ "/logs/" ++ Date.getDate.format("%y%m%d-%H%M") ++ "t.log"; q.init.value; }; SCButton(conwin, Rect(100,0,80,20)).states_([["Set 1"]]).action = { |but| // Kurtosis q.etest.value; q.files = (q.dir ++ "/kurtosis/*.aiff").pathMatch; q.logFilename = q.dir ++ "/logs/" ++ Date.getDate.format("%y%m%d-%H%M") ++ "s1.log"; q.init.value; }; SCButton(conwin, Rect(190,0,80,20)).states_([["Set 2"]]).action = { |but| // Generic q.etest.value; q.files = (q.dir ++ "/signals/*.aiff").pathMatch; q.logFilename = q.dir ++ "/logs/" ++ Date.getDate.format("%y%m%d-%H%M") ++ "s2.log"; q.init.value; }; // initialisation q.init = { q.log = File.new(q.logFilename, "w"); q.log.write("#\tFile1\tFile2\tSeq\tCorrect\tReplays\n"); // overall (correct) answers q.answers = 0; q.corrAnswers = 0; q.replays = 0; q.routine.value; q.tgui.value; }; q.etest = { try { { win.close; }.defer; Tdef(\present).stop; }; }; q.tgui = { // the gui for the testing window width = 500; height = 200; win = SCWindow("Time series analysis experiment", Rect(300, 400, width, height)).front; win.view.decorator = layout = FlowLayout(win.bounds.copy.left_(0).top_(0), 2@0, 1@0); layout.gap_(Point(10,5)).margin_(Point(10,5)); layout.shift(10, 15); // status line info = SCStaticText(win, Rect(0,0,width-100, 15)).string_("Please press start when ready"); // start button q.sBtn = SCButton(win, Rect(0,0,60,20)).states_([["Start"],["Exit"]]); q.sBtn.action = { |but| if (but.value == 0, { q.log.write("\n\n-----------------------------\n"); q.log.write("Correct answers: " + q.corrAnswers.asString + " of " + q.answers.asString); q.log.write("\n -------- User Exit --------"); q.log.close; info.string_("Thank you for your attendance!"); q.etest.value; },{ q.rtn.play; }); }; // three buttons layout.nextLine.shift(0, 25); q.btns = ["Sample 1", "Sample 2", "Sample 3"].collect { |name| var but; but = SCButton(win, Rect(0,0,(width-50)/3, 30)); but.states_([[name, Color.black, Color(0,0,0,0)], [name, Color.black, Color.green]]); but.action = { |but| if (q.btns.collect(_.value).sum > 1, { q.btns.collect(_.value); if (q.seq == q.btns.collect(_.value), { q.log.write("1\t"); q.corrAnswers = q.corrAnswers + 1; }, { q.log.write("0\t"); }); q.answers = q.answers + 1; q.rtn.next; }); }; }; // volume control layout.nextLine.shift(0,20); q.vol = EZSlider(win, 300 @ 15, "Volume", ControlSpec(0,1.0, \lin, 0.1), { |v| q.player.set(\vol, v); }, 0.7); // replay button layout.nextLine.shift(width/2-40,30); q.rplBtn = SCButton(win, Rect(0,0,80,20)).states_([["Replay"]]); q.rplBtn.action = { |but| Tdef(\present).stop.play; q.replays = q.replays + 1; }; }; // Synth: random start position, random forward/backward SynthDef(\player, { |bufnum=0, vol=0.6, spos=0| Out.ar(0, EnvGen.kr(Env.linen(0.2, 3, 0.2, vol, 'sine'), doneAction: 2 ) * PlayBuf.ar(1, bufnum, BufRateScale.kr(bufnum)*(IRand(0,1)*2 -1), startPos: spos, loop: 1) ! 2 ) }).send(s); // Task for presentation Tdef(\present, { q.bufSeq.do { |b, i| var states; // not sure if to jitter with start position q.player = Synth(\player, [\bufnum, b, \vol, q.vol.value, \spos, (2*441000).rand]); { states = q.btns.at(i).states; states[0][2] = Color.red; q.btns.at(i).states = states; q.btns.at(i).refresh; }.defer; 3.5.wait; { states[0][2] = Color(0,0,0,0); q.btns.at(i).states = states; q.btns.at(i).refresh }.defer; } }); q.routine = { q.buf1 = q.buf2 = q.buf3 = nil; q.rtn = Routine({ q.files.do { |f1, i| var s1, s2, k1, k2, f3; var f2 = q.files.choose; // make sure we are not comparing the same file while ({ f1 == f2 }, { f2 = q.files.choose; "same file chosen".postln; }); // get the corresponding scrambled file f3 = q.scrFiles.select({ |f| f.asString.split.last == f1.asString.split.last }); if (f3.size != 1, { q.log.write("\n\n--------------------------------\n"); q.log.write("Fatal error - no corresponding scrambled sound file for" + f1.asString); }); { info.string_("Example " + (i+1).asString + " of " + q.files.size); }.defer; { q.btns.do(_.value_(0)); }.defer(0.5); if (q.buf1.isNil, {}, { q.buf1.free }); if (q.buf2.isNil, {}, { q.buf2.free }); if (q.buf3.isNil, {}, { q.buf3.free }); q.replays = 0; q.log.write(i.asString ++ "\t"); q.log.write(f1.asString.split.last ++ "\t"); q.log.write(f2.asString.split.last ++ "\t"); q.buf1 = Buffer.read(s, f1, action: { // tested q.buf2 = Buffer.read(s, f2, action: { // random other q.buf3 = Buffer.read(s, f3[0], action: { // tested scrambled q.bufSeq = [q.buf1.bufnum, q.buf2.bufnum, q.buf3.bufnum].scramble; q.seq = q.bufSeq.collect({ |i| if (i==q.buf2.bufnum, {0}, {1}) }); q.log.write(q.seq.asString ++ "\t"); Tdef(\present).stop.play; }); }); }); "ready".yield; q.log.write(q.replays.asString ++ "\n"); q.log.flush; }; q.log.write("\n\n--------------------------------\n"); q.log.write("Correct answers: " + q.corrAnswers.asString + " of " + q.answers.asString); q.log.write("\n -------- End --------"); q.log.close; { info.string_("Thank you for your attendance!"); }.defer; q.etest.value; }); }; ) /* --------------------------------------------------------------------------------- */ // Helper functions // calc skew and kurtosis for all signals ( q.skinfo = File.new(q.dir ++ "/info.csv", "w"); q.files.do { |f| var file = SoundFile.new; var array = FloatArray.newClear(44100 * 10); file.openRead(f.postln); file.readData(array); array.size.postln; q.skinfo.write(f.asString.split.last ++ "\t" ++ array.variance ++ "\t" ++ array.skew ++ "\t" ++ array.kurtosis ++ "\t" ++ array.minItem ++ "\t" ++ array.maxItem ++ "\n"); }; q.skinfo.close; ) // create scrambled files for all the sound files ( var server = Server.default; q.files.do { |f| var buf; buf = Buffer.read(server, f.asString, action: { |b| b.loadToFloatArray(action: { arg a; var astr, ascr, fascr, fout; fout = SoundFile.new; fout.openWrite(q.dir ++ "/scrambled/" ++ f.asString.split.last); astr = Array.fill(a.size, { |i| a[i] }); // not very nice ascr = astr.scramble; fascr = ascr.collectAsÊ({ |i| i }, FloatArray); // not very nice neither... fout.writeData(fascr); fout.close; buf.free; }); }); }; )