diff --git a/Builds/MacOSX/archive-for-distribution.sh b/Builds/MacOSX/archive-for-distribution.sh index 1863083..98f2fd4 100755 --- a/Builds/MacOSX/archive-for-distribution.sh +++ b/Builds/MacOSX/archive-for-distribution.sh @@ -30,7 +30,8 @@ trap 'error ${LINENO}' ERR #### -declare -a BUILDS=("Debug" "Release") +# declare -a BUILDS=("Debug" "Release") +declare -a BUILDS=("Release") for BUILD in "${BUILDS[@]}" do BUILDROOT="$MYDIR/build" diff --git a/Builds/MacOSX/juicysfplugin.xcodeproj/project.pbxproj b/Builds/MacOSX/juicysfplugin.xcodeproj/project.pbxproj index 4f4dd07..fa94fdd 100644 --- a/Builds/MacOSX/juicysfplugin.xcodeproj/project.pbxproj +++ b/Builds/MacOSX/juicysfplugin.xcodeproj/project.pbxproj @@ -294,6 +294,7 @@ 358E45BB22BEE53A0087ED8D /* libgthread-2.0.0.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = "libgthread-2.0.0.dylib"; sourceTree = ""; }; 358E45BC22BEE53A0087ED8D /* libFLAC.8.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = libFLAC.8.dylib; sourceTree = ""; }; 358E45BD22BEE53A0087ED8D /* libogg.0.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = libogg.0.dylib; sourceTree = ""; }; + 358E45F422BFC00C0087ED8D /* MidiConstants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = MidiConstants.h; path = ../../Source/MidiConstants.h; sourceTree = ""; }; 35D551D55292C9D0508A408A /* PluginEditor.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = PluginEditor.cpp; path = ../../Source/PluginEditor.cpp; sourceTree = SOURCE_ROOT; }; 373EF982A53046CE00BECE68 /* include_juce_events.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = include_juce_events.mm; path = ../../JuceLibraryCode/include_juce_events.mm; sourceTree = SOURCE_ROOT; }; 3909EE4609ED2DCCC6B6B290 /* juce_data_structures */ = {isa = PBXFileReference; lastKnownFileType = folder; name = juce_data_structures; path = /Applications/JUCE/modules/juce_data_structures; sourceTree = ""; }; @@ -545,6 +546,7 @@ 420DCC01988E65E68562F9DC /* PluginProcessor.h */, 35D551D55292C9D0508A408A /* PluginEditor.cpp */, 8990F3EAFFBBD6A42247C663 /* PluginEditor.h */, + 358E45F422BFC00C0087ED8D /* MidiConstants.h */, ); name = Source; sourceTree = ""; diff --git a/Source/FluidSynthModel.cpp b/Source/FluidSynthModel.cpp index f9a4301..a14552c 100644 --- a/Source/FluidSynthModel.cpp +++ b/Source/FluidSynthModel.cpp @@ -4,6 +4,7 @@ #include #include "FluidSynthModel.h" +#include "MidiConstants.h" using namespace std; @@ -15,8 +16,8 @@ FluidSynthModel::FluidSynthModel(SharesParams& p) currentSampleRate(44100), initialised(false), sfont_id(0), - channel(0), - mod(nullptr) + channel(0)/*, + mod(nullptr)*/ {} @@ -27,7 +28,7 @@ FluidSynthModel::~FluidSynthModel() { delete_fluid_settings(settings); // delete driver; // delete settings; - delete_fluid_mod(mod); +// delete_fluid_mod(mod); } } @@ -43,7 +44,7 @@ void FluidSynthModel::initialise() { settings = new_fluid_settings(); // https://sourceforge.net/p/fluidsynth/wiki/FluidSettings/ -// fluid_settings_setint(settings, "synth.verbose", 1); + fluid_settings_setint(settings, "synth.verbose", 1); synth = new_fluid_synth(settings); fluid_synth_set_sample_rate(synth, currentSampleRate); @@ -63,36 +64,73 @@ void FluidSynthModel::initialise() { // changePreset(128, 13); + float env_amount(12700.0f); - -// mod = new_fluid_mod(); -// -// // modulator's primary source controller and flags -// // fluid_mod_src: -// // https://github.com/FluidSynth/fluidsynth/blob/master/include/fluidsynth/mod.h#L61 -// // fluid_mod_flags: -// // https://github.com/FluidSynth/fluidsynth/blob/master/include/fluidsynth/mod.h#L41 -// // diagrams showing what negative and concave mean: -// // https://musescore.org/en/user/527826/blog/2016/05/23/volume-fluidsynth -// // fluid_gen_type: -// // https://github.com/FluidSynth/fluidsynth/blob/master/include/fluidsynth/gen.h#L36 -// // https://github.com/FluidSynth/fluidsynth/blob/master/src/synth/fluid_gen.c#L27 -// fluid_mod_set_source1(mod, -// FLUID_MOD_KEYPRESSURE, -// FLUID_MOD_CC | -// FLUID_MOD_POSITIVE | -// FLUID_MOD_UNIPOLAR | -// FLUID_MOD_CONCAVE); -// // modulator's secondary source controller and flags -// // MIDI CC 74 -// fluid_mod_set_source2(mod, 74, FLUID_MOD_CC); -// // generator for filter cutoff -// fluid_mod_set_dest(mod, GEN_FILTERFC); -// fluid_mod_set_amount(mod, 13500.0f); -// -// fluid_synth_add_default_mod(synth, mod, FLUID_SYNTH_ADD); + fluid_mod_t *mod(new_fluid_mod()); + fluid_mod_set_source1(mod, + static_cast(SOUND_CTRL2), // MIDI CC 71 Timbre/Harmonic Intensity (filter resonance) + FLUID_MOD_CC + | FLUID_MOD_UNIPOLAR + | FLUID_MOD_CONCAVE + | FLUID_MOD_NEGATIVE); + fluid_mod_set_source2(mod, 0, 0); + fluid_mod_set_dest(mod, GEN_FILTERQ); + fluid_mod_set_amount(mod, FLUID_PEAK_ATTENUATION); + fluid_synth_add_default_mod(synth, mod, FLUID_SYNTH_ADD); + delete_fluid_mod(mod); + mod = new_fluid_mod(); + fluid_mod_set_source1(mod, + static_cast(SOUND_CTRL3), // MIDI CC 72 Release time + FLUID_MOD_CC + | FLUID_MOD_BIPOLAR + | FLUID_MOD_CONCAVE + | FLUID_MOD_POSITIVE); + fluid_mod_set_source2(mod, 0, 0); + fluid_mod_set_dest(mod, GEN_VOLENVRELEASE); + fluid_mod_set_amount(mod, env_amount); + fluid_synth_add_default_mod(synth, mod, FLUID_SYNTH_ADD); + delete_fluid_mod(mod); + + mod = new_fluid_mod(); + fluid_mod_set_source1(mod, + static_cast(SOUND_CTRL4), // MIDI CC 73 Attack time + FLUID_MOD_CC + | FLUID_MOD_BIPOLAR + | FLUID_MOD_CONCAVE + | FLUID_MOD_POSITIVE); + fluid_mod_set_source2(mod, 0, 0); + fluid_mod_set_dest(mod, GEN_VOLENVATTACK); + fluid_mod_set_amount(mod, env_amount); + fluid_synth_add_default_mod(synth, mod, FLUID_SYNTH_ADD); + delete_fluid_mod(mod); + + mod = new_fluid_mod(); + fluid_mod_set_source1(mod, + static_cast(SOUND_CTRL5), // MIDI CC 74 Brightness (cutoff frequency, FILTERFC) + FLUID_MOD_CC + | FLUID_MOD_SWITCH + | FLUID_MOD_UNIPOLAR + | FLUID_MOD_POSITIVE); + fluid_mod_set_source2(mod, 0, 0); + fluid_mod_set_dest(mod, GEN_FILTERFC); + fluid_mod_set_amount(mod, -2400.0f); + fluid_synth_add_default_mod(synth, mod, FLUID_SYNTH_ADD); + delete_fluid_mod(mod); + + mod = new_fluid_mod(); + fluid_mod_set_source1(mod, + static_cast(SOUND_CTRL6), // MIDI CC 75 Decay Time + FLUID_MOD_CC + | FLUID_MOD_BIPOLAR + | FLUID_MOD_CONCAVE + | FLUID_MOD_POSITIVE); + fluid_mod_set_source2(mod, 0, 0); + fluid_mod_set_dest(mod, GEN_VOLENVDECAY); + fluid_mod_set_amount(mod, env_amount); + fluid_synth_add_default_mod(synth, mod, FLUID_SYNTH_ADD); + delete_fluid_mod(mod); initialised = true; } diff --git a/Source/FluidSynthModel.h b/Source/FluidSynthModel.h index 4702141..11e7a49 100644 --- a/Source/FluidSynthModel.h +++ b/Source/FluidSynthModel.h @@ -88,7 +88,7 @@ private: unsigned int sfont_id; unsigned int channel; - fluid_mod_t* mod; +// fluid_mod_t* mod; ListenerList eventListeners; diff --git a/Source/MidiConstants.h b/Source/MidiConstants.h new file mode 100644 index 0000000..a5fef83 --- /dev/null +++ b/Source/MidiConstants.h @@ -0,0 +1,164 @@ +#pragma once + +/* Taken from: + * fluidsynth/src/midi/fluid_midi.h + * fluidsynth/src/utils/fluid_conv_tables.h + * https://github.com/FluidSynth/fluidsynth/blob/master/src/midi/fluid_midi.h + * https://github.com/FluidSynth/fluidsynth/blob/master/src/utils/fluid_conv_tables.h + * + * FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 Peter Hanappe and others. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + +enum fluid_midi_event_type +{ + /* channel messages */ + NOTE_OFF = 0x80, + NOTE_ON = 0x90, + KEY_PRESSURE = 0xa0, + CONTROL_CHANGE = 0xb0, + PROGRAM_CHANGE = 0xc0, + CHANNEL_PRESSURE = 0xd0, + PITCH_BEND = 0xe0, + /* system exclusive */ + MIDI_SYSEX = 0xf0, + /* system common - never in midi files */ + MIDI_TIME_CODE = 0xf1, + MIDI_SONG_POSITION = 0xf2, + MIDI_SONG_SELECT = 0xf3, + MIDI_TUNE_REQUEST = 0xf6, + MIDI_EOX = 0xf7, + /* system real-time - never in midi files */ + MIDI_SYNC = 0xf8, + MIDI_TICK = 0xf9, + MIDI_START = 0xfa, + MIDI_CONTINUE = 0xfb, + MIDI_STOP = 0xfc, + MIDI_ACTIVE_SENSING = 0xfe, + MIDI_SYSTEM_RESET = 0xff, + /* meta event - for midi files only */ + MIDI_META_EVENT = 0xff +}; + +enum fluid_midi_control_change +{ + BANK_SELECT_MSB = 0x00, + MODULATION_MSB = 0x01, + BREATH_MSB = 0x02, + FOOT_MSB = 0x04, + PORTAMENTO_TIME_MSB = 0x05, + DATA_ENTRY_MSB = 0x06, + VOLUME_MSB = 0x07, + BALANCE_MSB = 0x08, + PAN_MSB = 0x0A, + EXPRESSION_MSB = 0x0B, + EFFECTS1_MSB = 0x0C, + EFFECTS2_MSB = 0x0D, + GPC1_MSB = 0x10, /* general purpose controller */ + GPC2_MSB = 0x11, + GPC3_MSB = 0x12, + GPC4_MSB = 0x13, + BANK_SELECT_LSB = 0x20, + MODULATION_WHEEL_LSB = 0x21, + BREATH_LSB = 0x22, + FOOT_LSB = 0x24, + PORTAMENTO_TIME_LSB = 0x25, + DATA_ENTRY_LSB = 0x26, + VOLUME_LSB = 0x27, + BALANCE_LSB = 0x28, + PAN_LSB = 0x2A, + EXPRESSION_LSB = 0x2B, + EFFECTS1_LSB = 0x2C, + EFFECTS2_LSB = 0x2D, + GPC1_LSB = 0x30, + GPC2_LSB = 0x31, + GPC3_LSB = 0x32, + GPC4_LSB = 0x33, + SUSTAIN_SWITCH = 0x40, + PORTAMENTO_SWITCH = 0x41, + SOSTENUTO_SWITCH = 0x42, + SOFT_PEDAL_SWITCH = 0x43, + LEGATO_SWITCH = 0x44, + HOLD2_SWITCH = 0x45, + SOUND_CTRL1 = 0x46, + SOUND_CTRL2 = 0x47, + SOUND_CTRL3 = 0x48, + SOUND_CTRL4 = 0x49, + SOUND_CTRL5 = 0x4A, + SOUND_CTRL6 = 0x4B, + SOUND_CTRL7 = 0x4C, + SOUND_CTRL8 = 0x4D, + SOUND_CTRL9 = 0x4E, + SOUND_CTRL10 = 0x4F, + GPC5 = 0x50, + GPC6 = 0x51, + GPC7 = 0x52, + GPC8 = 0x53, + PORTAMENTO_CTRL = 0x54, + EFFECTS_DEPTH1 = 0x5B, + EFFECTS_DEPTH2 = 0x5C, + EFFECTS_DEPTH3 = 0x5D, + EFFECTS_DEPTH4 = 0x5E, + EFFECTS_DEPTH5 = 0x5F, + DATA_ENTRY_INCR = 0x60, + DATA_ENTRY_DECR = 0x61, + NRPN_LSB = 0x62, + NRPN_MSB = 0x63, + RPN_LSB = 0x64, + RPN_MSB = 0x65, + ALL_SOUND_OFF = 0x78, + ALL_CTRL_OFF = 0x79, + LOCAL_CONTROL = 0x7A, + ALL_NOTES_OFF = 0x7B, + OMNI_OFF = 0x7C, + OMNI_ON = 0x7D, + POLY_OFF = 0x7E, + POLY_ON = 0x7F +}; + +/* + Attenuation range in centibels. + Attenuation range is the dynamic range of the volume envelope generator + from 0 to the end of attack segment. + fluidsynth is a 24 bit synth, it could (should??) be 144 dB of attenuation. + However the spec makes no distinction between 16 or 24 bit synths, so use + 96 dB here. + + Note about usefulness of 24 bits: + 1)Even fluidsynth is a 24 bit synth, this format is only relevant if + the sample format coming from the soundfont is 24 bits and the audio sample format + choosen by the application (audio.sample.format) is not 16 bits. + + 2)When the sample soundfont is 16 bits, the internal 24 bits number have + 16 bits msb and lsb to 0. Consequently, at the DAC output, the dynamic range of + this 24 bit sample is reduced to the the dynamic of a 16 bits sample (ie 90 db) + even if this sample is produced by the audio driver using an audio sample format + compatible for a 24 bit DAC. + + 3)When the audio sample format settings is 16 bits (audio.sample.format), the + audio driver will make use of a 16 bit DAC, and the dynamic will be reduced to 96 dB + even if the initial sample comes from a 24 bits soundfont. + + In both cases (2) or (3), the real dynamic range is only 96 dB. + + Other consideration for FLUID_NOISE_FLOOR related to case (1),(2,3): + - for case (1), FLUID_NOISE_FLOOR should be the noise floor for 24 bits (i.e -138 dB). + - for case (2) or (3), FLUID_NOISE_FLOOR should be the noise floor for 16 bits (i.e -90 dB). + */ +#define FLUID_PEAK_ATTENUATION 960.0f diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index ff89ccc..d3c12b6 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -13,6 +13,7 @@ #include "SoundfontSynthVoice.h" #include "SoundfontSynthSound.h" #include "ExposesComponents.h" +#include "MidiConstants.h" AudioProcessor* JUCE_CALLTYPE createPluginFilter(); @@ -153,6 +154,63 @@ void JuicySFAudioProcessor::processBlock (AudioBuffer& buffer, MidiBuffer // Now pass any incoming midi messages to our keyboard state object, and let it // add messages to the buffer if the user is clicking on the on-screen keys keyboardState.processNextMidiBuffer (midiMessages, 0, numSamples, true); + + MidiBuffer processedMidi; + int time; + MidiMessage m; + + // TODO: factor into a MidiCollector + for (MidiBuffer::Iterator i (midiMessages); i.getNextEvent (m, time);) { + Logger::outputDebugString ( m.getDescription() ); + + // explicitly not handling note_on/off, or pitch_bend, because these are (for better or worse) + // responsibilities of SoundfontSynthVoice. + // well, by that logic maybe I should move program change onto Voice. but it doesn't feel like a per-voice concern. + if (m.isController()) { + fluid_midi_event_t *midi_event(new_fluid_midi_event()); + fluid_midi_event_set_type(midi_event, static_cast(CONTROL_CHANGE)); + fluid_midi_event_set_channel(midi_event, fluidSynthModel.getChannel()); + fluid_midi_event_set_control(midi_event, m.getControllerNumber()); + fluid_midi_event_set_value(midi_event, m.getControllerValue()); + fluid_synth_handle_midi_event(fluidSynth, midi_event); + delete_fluid_midi_event(midi_event); + } else if (m.isProgramChange()) { + fluid_midi_event_t *midi_event(new_fluid_midi_event()); + fluid_midi_event_set_type(midi_event, static_cast(PROGRAM_CHANGE)); + fluid_midi_event_set_channel(midi_event, fluidSynthModel.getChannel()); + fluid_midi_event_set_program(midi_event, m.getProgramChangeNumber()); + fluid_synth_handle_midi_event(fluidSynth, midi_event); + delete_fluid_midi_event(midi_event); + } else if (m.isChannelPressure()) { + fluid_midi_event_t *midi_event(new_fluid_midi_event()); + fluid_midi_event_set_type(midi_event, static_cast(CHANNEL_PRESSURE)); + fluid_midi_event_set_channel(midi_event, fluidSynthModel.getChannel()); + fluid_midi_event_set_program(midi_event, m.getChannelPressureValue()); + fluid_synth_handle_midi_event(fluidSynth, midi_event); + delete_fluid_midi_event(midi_event); + } else if (m.isAftertouch()) { + fluid_midi_event_t *midi_event(new_fluid_midi_event()); + fluid_midi_event_set_type(midi_event, static_cast(KEY_PRESSURE)); + fluid_midi_event_set_channel(midi_event, fluidSynthModel.getChannel()); + fluid_midi_event_set_key(midi_event, m.getNoteNumber()); + fluid_midi_event_set_value(midi_event, m.getAfterTouchValue()); + fluid_synth_handle_midi_event(fluidSynth, midi_event); + delete_fluid_midi_event(midi_event); + } else if (m.isMetaEvent()) { + fluid_midi_event_t *midi_event(new_fluid_midi_event()); + fluid_midi_event_set_type(midi_event, static_cast(MIDI_SYSTEM_RESET)); + fluid_synth_handle_midi_event(fluidSynth, midi_event); + delete_fluid_midi_event(midi_event); + } else if (m.isSysEx()) { + fluid_midi_event_t *midi_event(new_fluid_midi_event()); + fluid_midi_event_set_type(midi_event, static_cast(MIDI_SYSEX)); + // I assume that the MidiMessage's sysex buffer would be freed anyway when MidiMessage is destroyed, so set dynamic=false + // to ensure that fluidsynth does not attempt to free the sysex buffer during delete_fluid_midi_event() + fluid_midi_event_set_sysex(midi_event, const_cast(m.getSysExData()), m.getSysExDataSize(), static_cast(false)); + fluid_synth_handle_midi_event(fluidSynth, midi_event); + delete_fluid_midi_event(midi_event); + } + } // and now get our synth to process these midi events and generate its output. synth.renderNextBlock (buffer, midiMessages, 0, numSamples); diff --git a/Source/SoundfontSynthVoice.cpp b/Source/SoundfontSynthVoice.cpp index bcf8dc6..30b6afa 100644 --- a/Source/SoundfontSynthVoice.cpp +++ b/Source/SoundfontSynthVoice.cpp @@ -58,12 +58,17 @@ void SoundfontSynthVoice::stopNote (float /*velocity*/, bool allowTailOff) { clearCurrentNote(); fluid_synth_noteoff(synth, 0, this->midiNoteNumber); } -void SoundfontSynthVoice::pitchWheelMoved (int /*newValue*/) { - // who cares? + +// receives input as MIDI 0 to 16383, with 8192 being center +// this is also exactly the input fluidsynth requires +void SoundfontSynthVoice::pitchWheelMoved (int newValue) { + Logger::outputDebugString ( juce::String::formatted("Pitch wheel: %d\n", newValue) ); + fluid_synth_pitch_bend(synth, 0, newValue); } -void SoundfontSynthVoice::controllerMoved (int /*controllerNumber*/, int /*newValue*/) { - // what's a controller? +void SoundfontSynthVoice::controllerMoved (int controllerNumber, int newValue) { + // this seems to be "program change" event + Logger::outputDebugString ( juce::String::formatted("Controller moved: %d, %d\n", controllerNumber, newValue) ); } void SoundfontSynthVoice::renderNextBlock (AudioBuffer& outputBuffer, int startSample, int numSamples) { @@ -94,4 +99,4 @@ void SoundfontSynthVoice::renderNextBlock (AudioBuffer& outputBuffer, int // } // } // } -//} \ No newline at end of file +//}