// fix dc offset and BAD noise problem from built-in mic input // h. james harkins, 19 dec 2011 // boot up, set jack connections // take a noise sample, fft it, and save the fft back to the server // run the noise-canceling synth var noiseSampSize = 4096; QtGUI.style = \Plastique; if(SCJConnection.connections.isNil) { { var win = Window("Error", Rect.aboutPoint(Window.screenBounds.center, 200, 32) ).onClose_({ 0.exit }); StaticText(win, Rect(2, 5, 396, 20)) .align_(\center) .string_("Please start the JACK server before running this script.".postln); Button(win, Rect(160, 33, 80, 20)) .states_([["exit"]]) .action_({ win.onClose = nil; 0.exit }); win.front; }.value; } { // this sclang client should not kill servers that it didn't start // so empty out the set Server.set.copy.do { |server| if(server.window.notNil) { server.window.close }; Server.set.remove(server); Server.named.removeAt(server.name.asSymbol); }; // boot up, set jack connections z = Server(\audioinfix, NetAddr("127.0.0.1", 57119), ServerOptions.new .numInputBusChannels_(2) .numOutputBusChannels_(2) // need the device name so that I can predict the jack client name .device_("scsynth_micfix") ); z.waitForBoot { var ports, cond = Condition.new, samplebuf = Buffer.alloc(z, noiseSampSize, 1), noiseRecFunc, watcher, synth, data, hamm, fftdata, fftbuf, amp, nrfactor = 3, nreditor, win, peakmon, peakmonWindow, peakmonButton; SCJConnection.getconnections; SCJConnection.getallports; "got connections".debug; ports = SCJConnection.allports; SCJConnection.connect( [ 'scsynth_micfix:out_1', 'scsynth_micfix:out_2' ].collect(ports.findKeyForValue(_)), [ 'PulseAudio JACK Source:front-left', 'PulseAudio JACK Source:front-right' ].collect(ports.findKeyForValue(_)) ); NotificationCenter.registerOneShot(SCJConnection, \connectDone, \me, { cond.unhang; }); cond.hang; "finished connecting".debug; SCJConnection.disconnect( [ 'scsynth_micfix:out_1', 'scsynth_micfix:out_2', 'system:capture_1', 'system:capture_2' ].collect(ports.findKeyForValue(_)), [ 'system:playback_1', 'system:playback_2', 'PulseAudio JACK Source:front-left', 'PulseAudio JACK Source:front-right' ].collect(ports.findKeyForValue(_)) ); NotificationCenter.registerOneShot(SCJConnection, \disconnectDone, \me, { cond.unhang; }); cond.hang; "finished disconnecting".debug; // take a noise sample, fft it, and save the fft back to the server SynthDef(\rec, { RecordBuf.ar(LeakDC.ar(SoundIn.ar(0)), samplebuf, loop: 0, doneAction: 2); }).send(z); z.sync; noiseRecFunc = { var synth = Synth(\rec, target: z); watcher = OSCFunc({ |msg| if(msg[1] == synth.nodeID) { watcher.free; cond.unhang; }; }, '/n_end', z.addr); cond.hang; "recorded".debug; fork { samplebuf.getToFloatArray(wait: 0.05, action: { |data_in| data = data_in; cond.unhang; }); }; cond.hang; "got time domain".debug; hamm = Signal.hammingWindow(data.size); data = Signal.fill(data.size, { |i| data[i] * hamm[i] }); fftdata = (data * nrfactor).fft(Signal.newClear(data.size), Signal.fftCosTable(data.size)); fork { if(fftbuf.isNil) { fftbuf = Buffer.alloc(z, data.size*2, 1); z.sync; }; fftbuf.sendCollection([fftdata.real, fftdata.imag].flop.flat, 0, 0.05, { cond.unhang; }); }; cond.hang; "sent fft".debug; }; noiseRecFunc.value; // run the noise-canceling synth SynthDef(\nr, { |fftbuf, amp, maxLevel = 0.99| var sig = LeakDC.ar(SoundIn.ar(0)), fft = FFT(LocalBuf(BufFrames.ir(fftbuf)), sig), fftsource = FFTTrigger(fftbuf), fftsub = FFTTrigger(LocalBuf(BufFrames.ir(fftbuf))), copy = PV_Copy(fftsource, fftsub); fft = PV_MagSubtract(fft, fftsub, 1); Out.ar(0, Limiter.ar(IFFT(fft) * amp.dbamp, maxLevel) ! 2); }).send(z); SynthDef(\thru, { |amp, maxLevel = 0.99| var sig = LeakDC.ar(SoundIn.ar(0)); Out.ar(0, Limiter.ar(sig * amp.dbamp, maxLevel) ! 2); }).send(z); z.sync; amp = GenericGlobalControl(\amp, Bus.control(z, 1), 10, [-20, 20]); win = ResizeFlowWindow("NR", Rect(0, 0, 350, 200)); Button(win, 150@20).states_([["stop processing"]]) .action_({ { z.quit; // reset jack connections -- later SCJConnection.connect( [ 'system:capture_1', 'system:capture_2' ].collect(ports.findKeyForValue(_)), [ 'PulseAudio JACK Source:front-left', 'PulseAudio JACK Source:front-right' ].collect(ports.findKeyForValue(_)) ); NotificationCenter.registerOneShot(SCJConnection, \connectDone, \quitter, { cond.unhang; }); cond.hang; 0.exit; }.fork(AppClock); }); Button(win, 150@20) .states_([["new noise sample"]]) .action_({ fork(noiseRecFunc); }); Button(win, 150@20) .states_([ ["noise reduction ON", Color.black, Color.green], ["noise reduction off"/*, Color.black, Color.clear*/] ]) .action_({ |view| switch(view.value) { 0 } { if(synth.notNil) { synth.free }; synth = Synth(\nr, [fftbuf: fftbuf, amp: amp.asMap], z); } { 1 } { if(synth.notNil) { synth.free }; synth = Synth(\thru, [fftbuf: fftbuf, amp: amp.asMap], z); }; }) .valueAction_(0); peakmonButton = Button(win, 150@20) .states_([["PeakMonitor off"], ["PeakMonitor on", Color.black, Color.green]]) .action_({ |view| switch(view.value) { 0 } { peakmonWindow.close; } { 1 } { if(peakmon.isNil) { peakmon = PeakMonitor(Bus(\audio, 0, 4, z)); peakmonWindow = Window.allWindows.last; peakmonWindow.onClose = peakmonWindow.onClose.addFunc({ peakmon = nil; peakmonButton.value = 0; }); }; }; }); win.startRow; StaticText(win, 60@20).string_("amp").align_(\right); amp.gui(win); win.startRow; StaticText(win, 60@20).string_("NR factor").align_(\right); nreditor = NumberEditor(nrfactor, [1, 10].asSpec); nreditor.gui(win); SkipJack.new({ if(nrfactor != nreditor.value) { nrfactor = nreditor.value; fftdata = (data * nrfactor).fft( Signal.newClear(data.size), Signal.fftCosTable(data.size) ); fftbuf.sendCollection([fftdata.real, fftdata.imag].flop.flat, 0, 0.05, action: { nrfactor.debug("sent new fft for factor") }); }; }, dt: 0.2, name: \updatefft); win.recursiveResize.front; }; };