# Synth module. # # This script is more advanced, and some programming experience is # expected here. It's a synth module where we use two synths, one based # on FluidSynth, and the other based on ZynAddSubFx. This script # demonstrates most of the things jacktube is capable of, from # dymanic plugin loading to arrays and dynamic port names. # # The tricky part is that FluidSynth has a DSSI plugin, whereas # ZynAddSubFx does not (*), so we need to switch between those two # cases properly. Also take into account that we have many channels, # and the script becomes quite advanced. # # This script requires some outside setup too. To use it, do the # following: # # 1. Install the FluidSynth DSSI plugin. # 2. Put a set of soundfonts in a file called "instruments.sf2" and # put it in the current directory. # 3. Launch ZynAddSubFx with some patches. # 4. Run "jacktube -R complex-synth-module.jt". # 5. Connect the following jack ports: # ZynAddSubFx:left -> Jacktube:zyn_l # ZynAddSubFx:right -> Jacktube:zyn_r # Jacktube:out1_l -> system:playback_1 # Jacktube:out1_r -> system:playback_2 # The two last ones will be for channel 1 sound only. # 6. Connect the following MIDI sequencer ports: # :out -> Jacktube:in # Jacktube:out_zyn -> ZynAddSubFx:input # 7. Try playing your keyboard and switching banks. Bank zero is # FluidSynth, and bank one is Zyn. # (*) ZynAddSubFx has since gained a DSSI plugin, but this script # is useful nevertheless for educational purposes. # The following is a common concept in jacktube scripts: Status # variables hold various information about the state of the script, # which is used to match later on. # # Note the lack of a looping operator to assign consecutive numbers. # This is a deliberate design choice in the language: Jacktube # normally runs in a realtime environment, and this places constraints # on the amount of processing you can do. Since it is not easy to type # in large amounts of processing in a script, this forces you to think # about what you are entering. # # Each element of each array corresponds to one channel. The $plugins # array simply holds that very channel if the FluidSynth plugin is # active, and -1 otherwise. This is used to match events later on. $plugins[0] = 0; $plugins[1] = 1; $plugins[2] = 2; $plugins[3] = 3; $plugins[4] = 4; $plugins[5] = 5; $plugins[6] = 6; $plugins[7] = 7; $plugins[8] = 8; $plugins[9] = 9; $plugins[10] = 10; $plugins[11] = 11; $plugins[12] = 12; $plugins[13] = 13; $plugins[14] = 14; $plugins[15] = 15; # The $zyn array is like the $plugin array, but counts toward # ZynAddSubFx being active instead. $zyn[0] = -1; $zyn[1] = -1; $zyn[2] = -1; $zyn[3] = -1; $zyn[4] = -1; $zyn[5] = -1; $zyn[6] = -1; $zyn[7] = -1; $zyn[8] = -1; $zyn[9] = -1; $zyn[10] = -1; $zyn[11] = -1; $zyn[12] = -1; $zyn[13] = -1; $zyn[14] = -1; $zyn[15] = -1; # ZynAddSubFx only deals with channels, and doesn't have the concept of # program changes. Therefore we keep track of which patch we want on # ZynAddSubFx here, and translate that to the correct channel value # when we send events to Zyn. $zynprogram[0] = 0; $zynprogram[1] = 0; $zynprogram[2] = 0; $zynprogram[3] = 0; $zynprogram[4] = 0; $zynprogram[5] = 0; $zynprogram[6] = 0; $zynprogram[7] = 0; $zynprogram[8] = 0; $zynprogram[9] = 0; $zynprogram[10] = 0; $zynprogram[11] = 0; $zynprogram[12] = 0; $zynprogram[13] = 0; $zynprogram[14] = 0; $zynprogram[15] = 0; # We use banks to switch between Fluid and Zyn. Bank 0 is Fluid, 1 is # Zyn. # Bank MSB for each channel. We only send out both messages after both # have arrived. $bankMSB[0] = -1; $bankMSB[1] = -1; $bankMSB[2] = -1; $bankMSB[3] = -1; $bankMSB[4] = -1; $bankMSB[5] = -1; $bankMSB[6] = -1; $bankMSB[7] = -1; $bankMSB[8] = -1; $bankMSB[9] = -1; $bankMSB[10] = -1; $bankMSB[11] = -1; $bankMSB[12] = -1; $bankMSB[13] = -1; $bankMSB[14] = -1; $bankMSB[15] = -1; # Bank LSB for each channel. We only send out both messages after both # have arrived. $bankLSB[0] = -1; $bankLSB[1] = -1; $bankLSB[2] = -1; $bankLSB[3] = -1; $bankLSB[4] = -1; $bankLSB[5] = -1; $bankLSB[6] = -1; $bankLSB[7] = -1; $bankLSB[8] = -1; $bankLSB[9] = -1; $bankLSB[10] = -1; $bankLSB[11] = -1; $bankLSB[12] = -1; $bankLSB[13] = -1; $bankLSB[14] = -1; $bankLSB[15] = -1; # The final bank number after both MSB and LSB have been received. $bank[0] = 0; $bank[1] = 0; $bank[2] = 0; $bank[3] = 0; $bank[4] = 0; $bank[5] = 0; $bank[6] = 0; $bank[7] = 0; $bank[8] = 0; $bank[9] = 0; $bank[10] = 0; $bank[11] = 0; $bank[12] = 0; $bank[13] = 0; $bank[14] = 0; $bank[15] = 0; # Volumes for each channel. $volume[0] = 127; $volume[1] = 127; $volume[2] = 127; $volume[3] = 127; $volume[4] = 127; $volume[5] = 127; $volume[6] = 127; $volume[7] = 127; $volume[8] = 127; $volume[9] = 127; $volume[10] = 127; $volume[11] = 127; $volume[12] = 127; $volume[13] = 127; $volume[14] = 127; $volume[15] = 127; # Load and connect FluidSynth plugins for each channel. %instr[0] = "FluidSynth-DSSI"; %instr[0].configure("load", "instruments.sf2"); >~out1_l ~ %instr[0]["Output L"]; >~out1_r ~ %instr[0]["Output R"]; %instr[1] = "FluidSynth-DSSI"; %instr[1].configure("load", "instruments.sf2"); >~out2_l ~ %instr[1]["Output L"]; >~out2_r ~ %instr[1]["Output R"]; %instr[2] = "FluidSynth-DSSI"; %instr[2].configure("load", "instruments.sf2"); >~out3_l ~ %instr[2]["Output L"]; >~out3_r ~ %instr[2]["Output R"]; %instr[3] = "FluidSynth-DSSI"; %instr[3].configure("load", "instruments.sf2"); >~out4_l ~ %instr[3]["Output L"]; >~out4_r ~ %instr[3]["Output R"]; %instr[4] = "FluidSynth-DSSI"; %instr[4].configure("load", "instruments.sf2"); >~out5_l ~ %instr[4]["Output L"]; >~out5_r ~ %instr[4]["Output R"]; %instr[5] = "FluidSynth-DSSI"; %instr[5].configure("load", "instruments.sf2"); >~out6_l ~ %instr[5]["Output L"]; >~out6_r ~ %instr[5]["Output R"]; %instr[6] = "FluidSynth-DSSI"; %instr[6].configure("load", "instruments.sf2"); >~out7_l ~ %instr[6]["Output L"]; >~out7_r ~ %instr[6]["Output R"]; %instr[7] = "FluidSynth-DSSI"; %instr[7].configure("load", "instruments.sf2"); >~out8_l ~ %instr[7]["Output L"]; >~out8_r ~ %instr[7]["Output R"]; %instr[8] = "FluidSynth-DSSI"; %instr[8].configure("load", "instruments.sf2"); >~out9_l ~ %instr[8]["Output L"]; >~out9_r ~ %instr[8]["Output R"]; %instr[9] = "FluidSynth-DSSI"; %instr[9].configure("load", "instruments.sf2"); >~out10_l ~ %instr[9]["Output L"]; >~out10_r ~ %instr[9]["Output R"]; %instr[10] = "FluidSynth-DSSI"; %instr[10].configure("load", "instruments.sf2"); >~out11_l ~ %instr[10]["Output L"]; >~out11_r ~ %instr[10]["Output R"]; %instr[11] = "FluidSynth-DSSI"; %instr[11].configure("load", "instruments.sf2"); >~out12_l ~ %instr[11]["Output L"]; >~out12_r ~ %instr[11]["Output R"]; %instr[12] = "FluidSynth-DSSI"; %instr[12].configure("load", "instruments.sf2"); >~out13_l ~ %instr[12]["Output L"]; >~out13_r ~ %instr[12]["Output R"]; %instr[13] = "FluidSynth-DSSI"; %instr[13].configure("load", "instruments.sf2"); >~out14_l ~ %instr[13]["Output L"]; >~out14_r ~ %instr[13]["Output R"]; %instr[14] = "FluidSynth-DSSI"; %instr[14].configure("load", "instruments.sf2"); >~out15_l ~ %instr[14]["Output L"]; >~out15_r ~ %instr[14]["Output R"]; %instr[15] = "FluidSynth-DSSI"; %instr[15].configure("load", "instruments.sf2"); >~out16_l ~ %instr[15]["Output L"]; >~out16_r ~ %instr[15]["Output R"]; $fluidgain = "0.398107"; %instr[0].configure("GLOBAL:gain", $fluidgain); $fluidpoly = "64"; %instr[0].configure("GLOBAL:polyphony", $fluidpoly); # Match keys and pitchbend first, to minimize latency. final [<@in, all, type[keyon]-type[pitch], all, all] { if ($plugins[event[channel]] == event[channel]) { %instr[event[channel]] = [0, event[type], event[param], event[value]]; } else { >@out_zyn = [$zynprogram[event[channel]], event[type], event[key], event[value]]; } } # Switch between the banks. # Only when both MSB and LSB have been received do we switch. # Used to match bank switch. $bankMatcher = -1; # Used to match a final scope which prevents propogation of banks. $nonBankMatcher = -1; [<@in, all, type[control], 0, all] { $bankMatcher = -1; $nonBankMatcher = event[param]; $bankMSB[event[channel]] = event[value]; if ($bankLSB[event[channel]] != -1) { $bankMatcher = event[param]; } } [<@in, all, type[control], 32, all] { $bankMatcher = -1; $nonBankMatcher = event[param]; $bankLSB[event[channel]] = event[value]; if ($bankMSB[event[channel]] != -1) { $bankMatcher = event[param]; } } # This is where we do the actual bank switch from FluidSynth to Zyn and # vice versa. # "Local" variable. $bankToBe = 0; final [<@in, all, type[control], $bankMatcher, all] { $bankToBe = $bankMSB[event[channel]] * 128 + $bankLSB[event[channel]]; if ($bankToBe == 1) { $plugins[event[channel]] = -1; $zyn[event[channel]] = event[channel]; # Nifty dynamic port assigment to keep the audio bound to the channel. >~{"out" + int(event[channel] + 1) + "_l"} ~ <~zyn_l; >~{"out" + int(event[channel] + 1) + "_r"} ~ <~zyn_r; } else { $plugins[event[channel]] = event[channel]; $zyn[event[channel]] = -1; >~{"out" + int(event[channel] + 1) + "_l"} ~ %instr[event[channel]]["Output L"]; >~{"out" + int(event[channel] + 1) + "_r"} ~ %instr[event[channel]]["Output R"]; } $bank[event[channel]] = $bankToBe; $bankMSB[event[channel]] = -1; $bankLSB[event[channel]] = -1; } final [<@in, all, type[control], $nonBankMatcher, all] { # Prevent propogation of banks. stop; } final [<@in, $zyn[event[channel]], type[program], all, all] { $zynprogram[event[channel]] = event[value]; } final [<@in, all, type[program], all, all] { %instr[event[channel]] = [0, event[type], event[param], event[value]]; %instr[event[channel]] = [0, type[control], 7, $volume[event[channel]]]; } [<@in, all, type[control], 7, all] { # The volume propogation happens further down. $volume[event[channel]] = event[value]; } # GUI stuff. Use general purpose button CC 82 to launch GUI. final [<@in, $plugins[event[channel]], type[control], 82, 64-127] { %instr[event[channel]].show_gui("Channel " + int(event[channel] + 1), configure_print); } final [<@in, $plugins[event[channel]], type[control], 82, 0-63] { %instr[event[channel]].hide_gui(); } # Forward the rest of the MIDI messages. [<@in, $plugins[event[channel]], all, all, all] { %instr[event[channel]] = [0, event[type], event[param], event[value]]; } [<@in, $zyn[event[channel]], all, all, all] { >@out_zyn = [$zynprogram[event[channel]], event[type], event[key], event[value]]; }