diff --git a/Channel.pde b/Channel.pde index edb2a86..7d6cb5d 100644 --- a/Channel.pde +++ b/Channel.pde @@ -1,4 +1,5 @@ import processing.sound.*; +import java.util.Stack; public class ChannelOsc { @@ -6,7 +7,7 @@ public class ChannelOsc { Env[] circular_array_envs; int curr_env_index = 0; float curr_global_amp = 1.0; // channel volume (0.0 to 1.0) - float amp_multiplier = 1.0; // basically expression + float amp_multiplier = 1.0; // basically expression float curr_global_bend = 0.0; // channel pitch bend (-curr_bend_range to curr_bend_range semitones) float curr_global_pan = 0.0; // channel stereo panning (-1.0 to 1.0) float curr_bend_range = 2.0; // channel pitch bend range +/- semitones... uh, sure. @@ -20,7 +21,7 @@ public class ChannelOsc { float pulse_width = 0.5; ChannelDisplay disp; - final int CIRCULAR_ARR_SIZE = 32; + final int CIRCULAR_ARR_SIZE = 16; // values to be read by the display...: float last_amp = 0.0; @@ -65,7 +66,7 @@ public class ChannelOsc { if (curr_global_amp <= 0 || silenced) return; stop_note(note_code); - int mod_note_code = floor( note_code + curr_noteDetune + player.ktrans.transform[(note_code - 2 - player.mid_rootnote) % 12] ); + int mod_note_code = floor( note_code + curr_noteDetune + player.ktrans.transform[(note_code - 2 + player.mid_rootnote) % 12] ); float freq = midi_to_freq(mod_note_code); float amp = map(velocity, 0, 127, 0.0, 1.0); @@ -73,10 +74,10 @@ public class ChannelOsc { if (s == null) { s = get_new_osc(this.osc_type); s.freq(freq + curr_freqDetune); - s.pan(curr_global_pan); - s.amp(amp * (osc_type == 1 || osc_type == 2 ? 0.12 : 0.05) * curr_global_amp * amp_multiplier); // give a volume boost to TRI and SIN current_notes.put(note_code, s); } + s.pan(curr_global_pan); + s.amp(amp * (osc_type == 1 || osc_type == 2 ? 0.12 : 0.05) * curr_global_amp * amp_multiplier); // give a volume boost to TRI and SIN /*Env e = circular_array_envs[curr_env_index]; if (e == null) { @@ -85,9 +86,9 @@ public class ChannelOsc { }*/ if (osc_type == 0) ((Pulse) s).width(pulse_width); - if (env_values != null && env_values.length == 3) { + if (env_values != null && env_values.length == 4) { Env e = new Env(PARENT); - e.play(s, env_values[0], env_values[1], 1.0, env_values[2]); // will come back to envelopes... great potential but buggy :( + e.play(s, env_values[0], env_values[1], env_values[2], env_values[3]); // will come back to envelopes... great potential but buggy :( } else s.play(); diff --git a/LabsModule.pde b/LabsModule.pde index ed13b01..13bc4a5 100644 --- a/LabsModule.pde +++ b/LabsModule.pde @@ -10,7 +10,7 @@ public class LabsModule extends PApplet { public void settings() { - this.size(210, 220); + this.size(210, 290); } @@ -22,12 +22,12 @@ public class LabsModule extends PApplet { this.selfFrame = ( (PSurfaceAWT.SmoothCanvas)this.surface.getNative() ).getFrame(); reposition(); - redraw_all(); + this.redraw_all(); } public void draw() { - + this.redraw_all(); } @@ -40,7 +40,11 @@ public class LabsModule extends PApplet { b3.show_label = false; Button b4 = new Button("transform", "transform"); b4.show_label = false; - Button[] bs = new Button[] {b1, b2, b3, b4}; + Button b5 = new Button("sysSynth", "sysSynth"); + b5.show_label = false; + Button b6 = new Button("midiIn", "midiIn"); + b6.show_label = false; + Button[] bs = new Button[] {b1, b2, b3, b4, b5, b6}; all_buttons = new ButtonToolbar(8, 45, 0, 1.3, bs); } @@ -58,7 +62,7 @@ public class LabsModule extends PApplet { if (mouseButton == LEFT) { if (all_buttons.collided("freqDetune", this)) { try { - float val = Float.parseFloat(ui.showTextInputDialog("New detune in frequency?")); + float val = Float.parseFloat(ui.showTextInputDialog("New detune in Hz?")); player.set_all_freqDetune(val); } catch (NumberFormatException nfe) { @@ -69,7 +73,7 @@ public class LabsModule extends PApplet { else if (all_buttons.collided("noteDetune", this)) { try { - float val = Float.parseFloat(ui.showTextInputDialog("New detune in semitones?")); + float val = Float.parseFloat(ui.showTextInputDialog("New transpose in semitones?")); player.set_all_noteDetune(val); } catch (NumberFormatException nfe) { @@ -80,19 +84,19 @@ public class LabsModule extends PApplet { else if (all_buttons.collided("tempo", this)) { try { - int val = Integer.parseInt(ui.showTextInputDialog("New tempo in BPM?")); - if (val <= 0 || val > player.TEMPO_LIMIT) throw new NumberFormatException(); - player.seq.setTempoInBPM(val); + float val = Float.parseFloat(ui.showTextInputDialog("New speed factor?")); + if (val <= 0 || val > 4) throw new NumberFormatException(); + player.seq.setTempoFactor(val); } catch (NumberFormatException nfe) { - ui.showErrorDialog("Invalid value. Examples: 120, 90, 200", "Can't"); + ui.showErrorDialog("Invalid value. Examples: 0.1, 0.5, 1, 2.8", "Can't"); } catch (NullPointerException npe) {} } else if (all_buttons.collided("transform", this)) { String selection = new UiBooster().showSelectionDialog( - "New mode?", + "New key/chord mode?", "LabsModule", new ArrayList(player.ktrans.available_transforms.keySet()) ); @@ -103,9 +107,28 @@ public class LabsModule extends PApplet { curr_transform = selection; } } + + else if (all_buttons.collided("sysSynth", this)) { + ui.showWarningDialog( + "Volume is louder and some options are not\n" + + "available while System Synth is ON.\n" + + "Please mind the loading time.", + "LabsModule" + ); + PARENT.cursor(WAIT); + this.cursor(WAIT); + player.set_seq_synth(!player.system_synth); + PARENT.cursor(ARROW); + this.cursor(ARROW); + } + + else if (all_buttons.collided("midiIn", this)) { + if (!player.midi_in_mode) player.start_midi_in(); + else player.stop_midi_in(); + } } - this.redraw_all(); + //this.redraw_all(); } @@ -113,7 +136,9 @@ public class LabsModule extends PApplet { if (all_buttons.collided("freqDetune", this) || all_buttons.collided("noteDetune", this) || all_buttons.collided("tempo", this) || - all_buttons.collided("transform", this) + all_buttons.collided("transform", this) || + all_buttons.collided("sysSynth", this) || + all_buttons.collided("midiIn", this) ) { this.cursor(HAND); } @@ -136,13 +161,15 @@ public class LabsModule extends PApplet { this.textFont(fonts[2]); this.fill(t.theme[0]); this.textAlign(CENTER, CENTER); - this.text("Welcome to P3synth's\nexperimenting module!", this.width/2, 22); + this.text("Experimental options!\nUse at own risk.", this.width/2, 22); this.textFont(fonts[1]); this.text(player.last_freqDetune, 179, 60); this.text(player.last_noteDetune, 179, 99); - this.text(floor(player.seq.getTempoInBPM()), 179, 138); + this.text("x" + player.seq.getTempoFactor(), 179, 138); this.text(curr_transform, 179, 177); + this.text((player.system_synth ? "On" : "Off"), 179, 216); + this.text((player.midi_in_mode ? "On" : "Off"), 179, 255); all_buttons.redraw(this); } diff --git a/MIDInServer.py b/MIDInServer.py index 0250feb..efc1fd9 100644 --- a/MIDInServer.py +++ b/MIDInServer.py @@ -1,27 +1,59 @@ -import socket +import socket, time import mido +import mido.backends.rtmidi -HOST = "localhost" -PORT = 7726 -s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - -s.bind((HOST, PORT)) -s.listen(10) -conn, addr = s.accept() - -with mido.open_input(mido.get_input_names()[1]) as port: - while True: - msg = port.poll() - if msg is not None: - b = [str(x) for x in msg.bytes()] - try: - conn.send(f"{str(msg.channel)} {b[0]} {b[1]} {b[2]}\n".encode()) - except (AttributeError, IndexError): - continue - ''' - if midi_in.poll(): - event = midi_in.read(1) - conn.send((str(event[0][0]).replace("[", "").replace("]", "") + "\n").encode()) - ''' - -conn.close() +conn = None + +def main(): + global conn + print(" " + "_"*30 + "\n|_____P3synth_MIDIn_Server_____|") + print("Welcome! Warning: this is an unstable addon.\nFor detailed instructions, see the project's website.\n") + + HOST = "localhost" + PORT = 7723 + + for n, i in enumerate(mido.get_input_names()): + print(f"{n} .. {i}") + port_num = mido.get_input_names()[int(input("Which port to listen to? "))] + + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + print(f"\nSending to {HOST}:{PORT}...") + print("Please run P3synth and activate MIDI In mode!", end="\r") + + s.bind((HOST, PORT)) + s.listen(10) + conn, addr = s.accept() + print("OK!" + " "*50, end="\n\n") + + with mido.open_input(port_num) as port: + while True: + msg = port.poll() + if msg is not None: + b = [str(x) for x in msg.bytes()] + print("[*]", end="\r") + try: + conn.send(f"{str(msg.channel)} {b[0]} {b[1]} {b[2]}\n".encode()) + except (AttributeError, IndexError): + continue + except (BrokenPipeError, ConnectionResetError): + print("\nDisconnected!") + return + + else: + time.sleep(0.01) + print("[ ]", end="\r") + + +if __name__ == "__main__": + try: + main() + if conn is not None: + conn.close() + + except KeyboardInterrupt: + if conn is not None: + conn.send("goodbye".encode()) + conn.close() + + print("\nConnection closed.") diff --git a/P3synth.pde b/P3synth.pde index a5ab38d..6a96de0 100644 --- a/P3synth.pde +++ b/P3synth.pde @@ -4,7 +4,8 @@ import java.awt.*; import processing.awt.PSurfaceAWT; final processing.core.PApplet PARENT = this; -final float VERCODE = 22.81; +final float VERCODE = 22.89; +final float OVERALL_VOL = 0.7; Frame frame; Player player; @@ -29,7 +30,7 @@ void settings() { void setup() { - new Sound(PARENT).volume(0.7); // oscillators at volume 1 are ridiculously loud... + new Sound(PARENT).volume(OVERALL_VOL); // oscillators at volume 1 are ridiculously loud... SinOsc warmup = new SinOsc(PARENT); warmup.freq(100); @@ -59,7 +60,7 @@ void draw() { player.redraw(); if (player.playing_state != -1) { int n = (int) (player.seq.getTickPosition() / (player.midi_resolution/4)) % 8; - image(logo_anim[n], 311, 10); + image(logo_anim[abs(n)], 311, 10); } else { if (player.vu_anim_val >= 0.0) { @@ -83,7 +84,7 @@ void load_config(boolean just_opened) { catch (FileNotFoundException fnfe) { println("load fnfe"); ui.showWarningDialog( - "Welcome! You may want to lower the volume.\n\n" + + "Welcome! You may want to adjust your device's volume now.\n\n" + "Press PLAY to begin or EXIT to quit at any time.", "First time warning" @@ -131,6 +132,8 @@ void redraw_all() { b_meta_msgs.redraw(); b_reload_file.redraw(); b_labs.redraw(); + + if (player.syn != null) text(player.channels[0].curr_bend_range, 100, 20); } @@ -208,6 +211,8 @@ void mouseClicked() { media_buttons.get_button("Exit").set_pressed(true); //ui.showWaitingDialog("Exiting...", "Please wait"); player.set_playing_state(-1); + if (player.midi_in_mode) player.stop_midi_in(); + player.seq.close(); exit(); } @@ -240,7 +245,8 @@ void mouseClicked() { "PAUSE: pause any playing music or resume if paused.\n" + "EXIT: safely close the program.\n\n" + - "The buttons on the other side can control various options.\n\n" + + "The Labs menu has experimental playback/tinkering options!\n" + + "The buttons on the other side provide some info and configs.\n\n" + "Press the X on any channel to mute it.\n" + "You can use the lower left rectangle to control the song's position.\n" + @@ -307,9 +313,6 @@ void mouseClicked() { } b_labs.set_pressed(!b_labs.pressed); win_labs.reposition(); - - /* - */ } else { diff --git a/Player.pde b/Player.pde index 707b520..d27b0bc 100644 --- a/Player.pde +++ b/Player.pde @@ -2,6 +2,38 @@ import javax.sound.midi.*; import java.net.*; +void readMIDIn() { + try { + while (player.midi_in_mode) { + String in = player.stdIn.readLine(); + if (in != null) { + if (in.equals("goodbye")) break; + try { + ArrayList codes = new ArrayList(); + for(String s : in.split(" ")) codes.add(Integer.valueOf(s)); + ShortMessage msg = new ShortMessage( + codes.get(1), + codes.get(0), + codes.get(2), + codes.get(3) + ); + long t = 0; + player.event_listener.send(msg, t); + } + catch (InvalidMidiDataException imde) {} + } + } + + player.stop_midi_in(); + } + catch (IOException e) { + println("no socket???"); + player.stop_midi_in(); + } +} + + + class Player { Sequencer seq; KeyTransformer ktrans; @@ -19,6 +51,7 @@ class Player { int mid_scale = 0; // major int playing_state = -1; // -1 no loaded, 0 paused, 1 playing boolean midi_in_mode = false; + boolean system_synth = false; float vu_anim_val = 0.0; boolean vu_anim_returning = false; @@ -31,6 +64,20 @@ class Player { float last_noteDetune = 0.0; + Synthesizer syn; + void demoRPNbendrange() { + if (syn == null) { + try { + syn = MidiSystem.getSynthesizer(); + syn.open(); + } + catch (MidiUnavailableException mue) { println("mue on open"); } + } + syn.getChannels()[12].controlChange(101, 0); + syn.getChannels()[12].controlChange(6, 1); + } + + Player() { ktrans = new KeyTransformer(); channels = new ChannelOsc[16]; @@ -44,19 +91,7 @@ class Player { } create_display(0, 318); - - try { - seq = MidiSystem.getSequencer(false); - seq.open(); - seq.setLoopCount(-1); - - seq.addMetaEventListener(meta_listener); - Transmitter transmitter = seq.getTransmitter(); - transmitter.setReceiver(event_listener); - } - catch(MidiUnavailableException mue) { - println("Midi device unavailable!"); - } + set_seq_synth(false); } @@ -131,6 +166,80 @@ class Player { } + void set_seq_synth(boolean is_system) { + boolean playing_before = playing_state >= 0; + String prev_filename = curr_filename; + int prev_ticks = seq == null ? 0 : int(seq.getTickPosition()); + set_playing_state(-1); + + try { + if (seq != null) seq.close(); + seq = MidiSystem.getSequencer(is_system); + seq.open(); + seq.setLoopCount(-1); + seq.addMetaEventListener(meta_listener); + Transmitter transmitter = seq.getTransmitter(); + transmitter.setReceiver(event_listener); + + system_synth = is_system; + new Sound(PARENT).volume(is_system ? 0 : OVERALL_VOL); + } + catch(MidiUnavailableException mue) { + println("Midi device unavailable!"); + } + + if (playing_before) { + play_file(prev_filename); + setTicks(prev_ticks); + } + } + + + void start_midi_in() { + try { sock = new Socket("localhost",7723); } + catch (UnknownHostException uhe) { println("host???"); } + catch (IOException ioe) { + ui.showErrorDialog("MIDIn Server not started!", "Can't switch modes"); + return; + } + + sent = new Thread(new Runnable() { + @Override + public void run() { + try { + stdIn = new BufferedReader( + new InputStreamReader( + sock.getInputStream() + ) + ); + thread("readMIDIn"); + } + catch (IOException e) { println("no socket???"); } + } + }); + + sent.start(); + try { sent.join(); } + catch (InterruptedException e) { println("interrupted???"); } + + set_playing_state(-1); + midi_in_mode = true; + } + + + void stop_midi_in() { + midi_in_mode = false; + shut_up_all(); + sent.interrupt(); + try { + sock.close(); + stdIn.close(); + } + catch (IOException ioe) { println("ioe on close???"); } + ui.showInfoDialog("MIDI In disconnected!"); + } + + void set_all_freqDetune(float freq_detune) { last_freqDetune = freq_detune; for (ChannelOsc c : channels) { @@ -164,6 +273,8 @@ class Player { void set_playing_state(int how) { + if (seq == null) return; + how = constrain(how, -1, 1); switch (how) { case -1: @@ -227,6 +338,17 @@ class Player { int comm = event.getCommand(); int data1 = event.getData1(); int data2 = event.getData2(); + + //println(chan + " " + comm + " " + data1 + " " + data2); + /*try { + if (comm == ShortMessage.CONTROL_CHANGE && data1 == 6) { + println(chan + " " + comm + " " + data1 + " " + event.getData2()); + syn.getReceiver().send(new ShortMessage(ShortMessage.CONTROL_CHANGE, chan, data1, data2+10), timeStamp+10); + } + else syn.getReceiver().send(event, timeStamp+10); + } + catch (MidiUnavailableException mue) { println("mue on msg"); } + catch (InvalidMidiDataException imde) { println("imde on msg"); }*/ if (comm == ShortMessage.NOTE_ON && data2 > 0) { channels[chan].play_note(data1, data2); @@ -240,7 +362,7 @@ class Player { if (data1 >= 112) channels[chan].curr_global_amp = 0.0; else { channels[chan].set_osc_type(program_to_osc(data1)); - //channels[chan].set_env_values(program_to_env(data1)); + channels[chan].set_env_values(program_to_env(data1)); } } diff --git a/Resources.pde b/Resources.pde index 2be8dfa..1cb22b4 100644 --- a/Resources.pde +++ b/Resources.pde @@ -99,7 +99,7 @@ float program_to_osc(int prog) { float[] program_to_env(int prog) { - // attack time, sustain time, release time + // attack time, sustain time, sustain amp, release time return null; } diff --git a/UIServer.pde b/UIServer.pde index d3b109e..9a2fb5a 100644 --- a/UIServer.pde +++ b/UIServer.pde @@ -54,7 +54,7 @@ class ChannelDisplay { meter_ch_volume = parent.curr_global_amp * parent.amp_multiplier; meter_velocity = parent.last_amp; - int notecode = parent.last_notecode - 20; + int notecode = parent.last_notecode - 21; if (id == 9) { if (notecode == -1) label_note = "| |"; else label_note = "/ \\"; } else { if (notecode < 0) label_note = "-"; @@ -231,6 +231,10 @@ class PlayerDisplay { void redraw(boolean renew_values) { if (renew_values) update_all_values(); + fill(t.theme[2]); + noStroke(); + rect(58, y+42, 600, 20); + // Pos meter stroke(t.theme[0]); fill(t.theme[1]); diff --git a/data/buttons/freqDetuneUp.png b/data/buttons/freqDetuneUp.png index 5fc00d3..57128de 100644 Binary files a/data/buttons/freqDetuneUp.png and b/data/buttons/freqDetuneUp.png differ diff --git a/data/buttons/midiInUp.png b/data/buttons/midiInUp.png new file mode 100644 index 0000000..9d9de01 Binary files /dev/null and b/data/buttons/midiInUp.png differ diff --git a/data/buttons/sysSynthUp.png b/data/buttons/sysSynthUp.png new file mode 100644 index 0000000..4591ec5 Binary files /dev/null and b/data/buttons/sysSynthUp.png differ diff --git a/data/buttons/tempoUp.png b/data/buttons/tempoUp.png index 9009f54..9c99e8a 100644 Binary files a/data/buttons/tempoUp.png and b/data/buttons/tempoUp.png differ