--- gigedit/trunk/src/gigedit/dimregionedit.cpp 2009/02/03 19:38:19 1831 +++ gigedit/trunk/src/gigedit/dimregionedit.cpp 2017/07/23 18:31:53 3329 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006-2009 Andreas Persson + * Copyright (C) 2006-2017 Andreas Persson * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -17,19 +17,183 @@ * 02110-1301 USA. */ +#include "global.h" #include "dimregionedit.h" -#include "global.h" +#include "compat.h" + +VelocityCurve::VelocityCurve(double (gig::DimensionRegion::*getter)(uint8_t)) : + getter(getter), dimreg(0) { + set_size_request(80, 80); +} + +#if (GTKMM_MAJOR_VERSION == 2 && GTKMM_MINOR_VERSION < 90) || GTKMM_MAJOR_VERSION < 2 +bool VelocityCurve::on_expose_event(GdkEventExpose* e) { + const Cairo::RefPtr& cr = + get_window()->create_cairo_context(); +#if 0 +} +#endif +#else +bool VelocityCurve::on_draw(const Cairo::RefPtr& cr) { +#endif + if (dimreg) { + int w = get_width(); + int h = get_height(); + + for (int pass = 0 ; pass < 2 ; pass++) { + for (double x = 0 ; x <= w ; x++) { + int vel = int(x * (127 - 1e-10) / w + 1); + double y = (1 - (dimreg->*getter)(vel)) * (h - 3) + 1.5; + + if (x < 1e-10) { + cr->move_to(x, y); + } else { + cr->line_to(x, y); + } + } + if (pass == 0) { + cr->line_to(w, h); + cr->line_to(0, h); + cr->set_source_rgba(0.5, 0.44, 1.0, is_sensitive() ? 0.2 : 0.1); + cr->fill(); + } else { + cr->set_line_width(3); + cr->set_source_rgba(0.5, 0.44, 1.0, is_sensitive() ? 1.0 : 0.3); + cr->stroke(); + } + } + } + return true; +} + + +CrossfadeCurve::CrossfadeCurve() : dimreg(0) { + set_size_request(280, 80); +} + +#if (GTKMM_MAJOR_VERSION == 2 && GTKMM_MINOR_VERSION < 90) || GTKMM_MAJOR_VERSION < 2 +bool CrossfadeCurve::on_expose_event(GdkEventExpose* e) { + const Cairo::RefPtr& cr = + get_window()->create_cairo_context(); +#if 0 +} +#endif +#else +bool CrossfadeCurve::on_draw(const Cairo::RefPtr& cr) { +#endif + if (dimreg) { + cr->translate(1.5, 0); + + // first, draw curves for the other layers + gig::Region* region = dimreg->GetParent(); + int dimregno; + for (dimregno = 0 ; dimregno < region->DimensionRegions ; dimregno++) { + if (region->pDimensionRegions[dimregno] == dimreg) { + break; + } + } + int bitcount = 0; + for (int dim = 0 ; dim < region->Dimensions ; dim++) { + if (region->pDimensionDefinitions[dim].dimension == + gig::dimension_layer) { + int mask = + ~(((1 << region->pDimensionDefinitions[dim].bits) - 1) << + bitcount); + int c = dimregno & mask; // mask away the layer dimension + + for (int i = 0 ; i < region->pDimensionDefinitions[dim].zones ; + i++) { + gig::DimensionRegion* d = + region->pDimensionRegions[c + (i << bitcount)]; + if (d != dimreg) { + draw_one_curve(cr, d, false); + } + } + break; + } + bitcount += region->pDimensionDefinitions[dim].bits; + } + + // then, draw the currently selected layer + draw_one_curve(cr, dimreg, is_sensitive()); + } + return true; +} + +void CrossfadeCurve::draw_one_curve(const Cairo::RefPtr& cr, + const gig::DimensionRegion* d, + bool sensitive) { + int w = get_width(); + int h = get_height(); + + if (d->Crossfade.out_end) { + for (int pass = 0 ; pass < 2 ; pass++) { + cr->move_to(d->Crossfade.in_start / 127.0 * (w - 3), h); + cr->line_to(d->Crossfade.in_end / 127.0 * (w - 3), 1.5); + cr->line_to(d->Crossfade.out_start / 127.0 * (w - 3), 1.5); + cr->line_to(d->Crossfade.out_end / 127.0 * (w - 3), h); + + if (pass == 0) { + cr->set_source_rgba(0.5, 0.44, 1.0, sensitive ? 0.2 : 0.1); + cr->fill(); + } else { + cr->set_line_width(3); + cr->set_source_rgba(0.5, 0.44, 1.0, sensitive ? 1.0 : 0.3); + cr->stroke(); + } + } + } +} + + +EGStateOptions::EGStateOptions() : HBox(), + label(_("May be cancelled: ")), + checkBoxAttack(_("Attack")), + checkBoxAttackHold(_("Attack Hold")), + checkBoxDecay1(_("Decay 1")), + checkBoxDecay2(_("Decay 2")), + checkBoxRelease(_("Release")) +{ + set_spacing(6); + + pack_start(label); + pack_start(checkBoxAttack, Gtk::PACK_SHRINK); + pack_start(checkBoxAttackHold, Gtk::PACK_SHRINK); + pack_start(checkBoxDecay1, Gtk::PACK_SHRINK); + pack_start(checkBoxDecay2, Gtk::PACK_SHRINK); + pack_start(checkBoxRelease, Gtk::PACK_SHRINK); + + checkBoxAttack.set_tooltip_text(_( + "If checked: a note-off aborts the 'attack' stage." + )); + checkBoxAttackHold.set_tooltip_text(_( + "If checked: a note-off aborts the 'attack hold' stage." + )); + checkBoxDecay1.set_tooltip_text(_( + "If checked: a note-off aborts the 'decay 1' stage." + )); + checkBoxDecay2.set_tooltip_text(_( + "If checked: a note-off aborts the 'decay 2' stage." + )); + checkBoxRelease.set_tooltip_text(_( + "If checked: a note-on reverts back from the 'release' stage." + )); +} + DimRegionEdit::DimRegionEdit() : - eEG1PreAttack(_("Pre-attack"), 0, 100, 2), - eEG1Attack(_("Attack"), 0, 60, 3), - eEG1Decay1(_("Decay 1"), 0.005, 60, 3), - eEG1Decay2(_("Decay 2"), 0, 60, 3), + velocity_curve(&gig::DimensionRegion::GetVelocityAttenuation), + release_curve(&gig::DimensionRegion::GetVelocityRelease), + cutoff_curve(&gig::DimensionRegion::GetVelocityCutoff), + eEG1PreAttack(_("Pre-attack Level (%)"), 0, 100, 2), + eEG1Attack(_("Attack Time (seconds)"), 0, 60, 3), + eEG1Decay1(_("Decay 1 Time (seconds)"), 0.005, 60, 3), + eEG1Decay2(_("Decay 2 Time (seconds)"), 0, 60, 3), eEG1InfiniteSustain(_("Infinite sustain")), - eEG1Sustain(_("Sustain"), 0, 100, 2), - eEG1Release(_("Release"), 0, 60, 3), - eEG1Hold(_("Hold")), + eEG1Sustain(_("Sustain Level (%)"), 0, 100, 2), + eEG1Release(_("Release Time (seconds)"), 0, 60, 3), + eEG1Hold(_("Hold Attack Stage until Loop End")), eEG1Controller(_("Controller")), eEG1ControllerInvert(_("Controller invert")), eEG1ControllerAttackInfluence(_("Controller attack influence"), 0, 3), @@ -41,13 +205,13 @@ eLFO1Controller(_("Controller")), eLFO1FlipPhase(_("Flip phase")), eLFO1Sync(_("Sync")), - eEG2PreAttack(_("Pre-attack"), 0, 100, 2), - eEG2Attack(_("Attack"), 0, 60, 3), - eEG2Decay1(_("Decay 1"), 0.005, 60, 3), - eEG2Decay2(_("Decay 2"), 0, 60, 3), + eEG2PreAttack(_("Pre-attack Level (%)"), 0, 100, 2), + eEG2Attack(_("Attack Time (seconds)"), 0, 60, 3), + eEG2Decay1(_("Decay 1 Time (seconds)"), 0.005, 60, 3), + eEG2Decay2(_("Decay 2 Time (seconds)"), 0, 60, 3), eEG2InfiniteSustain(_("Infinite sustain")), - eEG2Sustain(_("Sustain"), 0, 100, 2), - eEG2Release(_("Release"), 0, 60, 3), + eEG2Sustain(_("Sustain Level (%)"), 0, 100, 2), + eEG2Release(_("Release Time (seconds)"), 0, 60, 3), eEG2Controller(_("Controller")), eEG2ControllerInvert(_("Controller invert")), eEG2ControllerAttackInfluence(_("Controller attack influence"), 0, 3), @@ -92,15 +256,19 @@ ePitchTrack(_("Pitch track")), eDimensionBypass(_("Dimension bypass")), ePan(_("Pan"), -64, 63), - eSelfMask(_("Self mask")), + eSelfMask(_("Kill lower velocity voices (a.k.a \"Self mask\")")), eAttenuationController(_("Attenuation controller")), eInvertAttenuationController(_("Invert attenuation controller")), eAttenuationControllerThreshold(_("Attenuation controller threshold")), eChannelOffset(_("Channel offset"), 0, 9), - eSustainDefeat(_("Sustain defeat")), - eMSDecode(_("MS decode")), + eSustainDefeat(_("Ignore Hold Pedal (a.k.a. \"Sustain defeat\")")), + eMSDecode(_("Decode Mid/Side Recordings")), eSampleStartOffset(_("Sample start offset"), 0, 2000), eUnityNote(_("Unity note")), + eSampleGroup(_("Sample Group")), + eSampleFormatInfo(_("Sample Format")), + eSampleID("Sample ID"), + eChecksum("Wave Data CRC-32"), eFineTune(_("Fine tune"), -49, 50), eGain(_("Gain"), -96, 0, 2, -655360), eGainPlus6(_("Gain +6dB"), eGain, 6 * -655360), @@ -110,8 +278,13 @@ eSampleLoopType(_("Loop type")), eSampleLoopInfinite(_("Infinite loop")), eSampleLoopPlayCount(_("Playback count"), 1), + buttonSelectSample(UNICODE_LEFT_ARROW + " " + _("Select Sample")), update_model(0) { + // make synthesis parameter page tabs scrollable + // (workaround for GTK3: default theme uses huge tabs which breaks layout) + set_scrollable(); + connect(eEG1PreAttack, &gig::DimensionRegion::EG1PreAttack); connect(eEG1Attack, &gig::DimensionRegion::EG1Attack); connect(eEG1Decay1, &gig::DimensionRegion::EG1Decay1); @@ -128,6 +301,26 @@ &gig::DimensionRegion::EG1ControllerDecayInfluence); connect(eEG1ControllerReleaseInfluence, &gig::DimensionRegion::EG1ControllerReleaseInfluence); + { + ClassMemberPtr mp(offsetof(gig::DimensionRegion, EG1Options.AttackCancel)); + connect(eEG1StateOptions.checkBoxAttack, mp.pmember); + } + { + ClassMemberPtr mp(offsetof(gig::DimensionRegion, EG1Options.AttackHoldCancel)); + connect(eEG1StateOptions.checkBoxAttackHold, mp.pmember); + } + { + ClassMemberPtr mp(offsetof(gig::DimensionRegion, EG1Options.Decay1Cancel)); + connect(eEG1StateOptions.checkBoxDecay1, mp.pmember); + } + { + ClassMemberPtr mp(offsetof(gig::DimensionRegion, EG1Options.Decay2Cancel)); + connect(eEG1StateOptions.checkBoxDecay2, mp.pmember); + } + { + ClassMemberPtr mp(offsetof(gig::DimensionRegion, EG1Options.ReleaseCancel)); + connect(eEG1StateOptions.checkBoxRelease, mp.pmember); + } connect(eLFO1Frequency, &gig::DimensionRegion::LFO1Frequency); connect(eLFO1InternalDepth, &gig::DimensionRegion::LFO1InternalDepth); connect(eLFO1ControlDepth, &gig::DimensionRegion::LFO1ControlDepth); @@ -149,6 +342,26 @@ &gig::DimensionRegion::EG2ControllerDecayInfluence); connect(eEG2ControllerReleaseInfluence, &gig::DimensionRegion::EG2ControllerReleaseInfluence); + { + ClassMemberPtr mp(offsetof(gig::DimensionRegion, EG2Options.AttackCancel)); + connect(eEG2StateOptions.checkBoxAttack, mp.pmember); + } + { + ClassMemberPtr mp(offsetof(gig::DimensionRegion, EG2Options.AttackHoldCancel)); + connect(eEG2StateOptions.checkBoxAttackHold, mp.pmember); + } + { + ClassMemberPtr mp(offsetof(gig::DimensionRegion, EG2Options.Decay1Cancel)); + connect(eEG2StateOptions.checkBoxDecay1, mp.pmember); + } + { + ClassMemberPtr mp(offsetof(gig::DimensionRegion, EG2Options.Decay2Cancel)); + connect(eEG2StateOptions.checkBoxDecay2, mp.pmember); + } + { + ClassMemberPtr mp(offsetof(gig::DimensionRegion, EG2Options.ReleaseCancel)); + connect(eEG2StateOptions.checkBoxRelease, mp.pmember); + } connect(eLFO2Frequency, &gig::DimensionRegion::LFO2Frequency); connect(eLFO2InternalDepth, &gig::DimensionRegion::LFO2InternalDepth); connect(eLFO2ControlDepth, &gig::DimensionRegion::LFO2ControlDepth); @@ -219,6 +432,9 @@ connect(eSampleLoopLength, &DimRegionEdit::set_LoopLength); connect(eSampleLoopInfinite, &DimRegionEdit::set_LoopInfinite); connect(eSampleLoopPlayCount, &DimRegionEdit::set_LoopPlayCount); + buttonSelectSample.signal_clicked().connect( + sigc::mem_fun(*this, &DimRegionEdit::onButtonSelectSamplePressed) + ); for (int i = 0 ; i < 7 ; i++) { table[i] = new Gtk::Table(3, 1); @@ -229,6 +445,9 @@ eUnityNote.set_tip( _("Note this sample is associated with (a.k.a. 'root note')") ); + buttonSelectSample.set_tooltip_text( + _("Selects the sample of this dimension region on the left hand side's sample tree view.") + ); eSampleStartOffset.set_tip(_("Sample position at which playback should be started")); ePan.set_tip(_("Stereo balance (left/right)")); eChannelOffset.set_tip( @@ -259,16 +478,77 @@ "Caution: this setting is stored on Sample side, thus is shared " "among all dimension regions that use this sample!") ); + + eEG1PreAttack.set_tip( + "Very first level this EG starts with. It rises then in Attack Time " + "seconds from this initial level to 100%." + ); + eEG1Attack.set_tip( + "Duration of the EG's Attack stage, which raises its level from " + "Pre-Attack Level to 100%." + ); + eEG1Hold.set_tip( + "On looped sounds, enabling this will cause the Decay 1 stage not to " + "enter before the loop has been passed one time." + ); + eAttenuationController.set_tip(_( + "If you are not using the 'Layer' dimension, then this controller " + "simply alters the volume. If you are using the 'Layer' dimension, " + "then this controller is controlling the crossfade between Layers in " + "real-time." + )); + + eLFO1Sync.set_tip( + "If not checked, every voice will use its own LFO instance, which " + "causes voices triggered at different points in time to have different " + "LFO levels. By enabling 'Sync' here the voices will instead use and " + "share one single LFO, causing all voices to have the same LFO level, " + "no matter when the individual notes have been triggered." + ); + eLFO2Sync.set_tip( + "If not checked, every voice will use its own LFO instance, which " + "causes voices triggered at different points in time to have different " + "LFO levels. By enabling 'Sync' here the voices will instead use and " + "share one single LFO, causing all voices to have the same LFO level, " + "no matter when the individual notes have been triggered." + ); + eLFO3Sync.set_tip( + "If not checked, every voice will use its own LFO instance, which " + "causes voices triggered at different points in time to have different " + "LFO levels. By enabling 'Sync' here the voices will instead use and " + "share one single LFO, causing all voices to have the same LFO level, " + "no matter when the individual notes have been triggered." + ); + eLFO1FlipPhase.set_tip( + "Inverts the LFO's generated wave vertically." + ); + eLFO2FlipPhase.set_tip( + "Inverts the LFO's generated wave vertically." + ); pageno = 0; rowno = 0; firstRowInBlock = 0; addHeader(_("Mandatory Settings")); - addString(_("Sample"), lSample, wSample); + addString(_("Sample"), lSample, wSample, buttonNullSampleReference); + buttonNullSampleReference->set_label("X"); + buttonNullSampleReference->set_tooltip_text(_("Remove current sample reference (NULL reference). This can be used to define a \"silent\" case where no sample shall be played.")); + buttonNullSampleReference->signal_clicked().connect( + sigc::mem_fun(*this, &DimRegionEdit::nullOutSampleReference) + ); //TODO: the following would break drag&drop: wSample->property_editable().set_value(false); or this: wSample->set_editable(false); - tooltips.set_tip(*wSample, _("Drop a sample here")); +#ifdef OLD_TOOLTIPS + tooltips.set_tip(*wSample, _("Drag & drop a sample here")); +#else + wSample->set_tooltip_text(_("Drag & drop a sample here")); +#endif addProp(eUnityNote); + addProp(eSampleGroup); + addProp(eSampleFormatInfo); + addProp(eSampleID); + addProp(eChecksum); + addRightHandSide(buttonSelectSample); addHeader(_("Optional Settings")); addProp(eSampleStartOffset); addProp(eChannelOffset); @@ -298,17 +578,18 @@ addHeader(_("Amplitude Envelope (EG1)")); addProp(eEG1PreAttack); addProp(eEG1Attack); + addProp(eEG1Hold); addProp(eEG1Decay1); addProp(eEG1Decay2); addProp(eEG1InfiniteSustain); addProp(eEG1Sustain); addProp(eEG1Release); - addProp(eEG1Hold); addProp(eEG1Controller); addProp(eEG1ControllerInvert); addProp(eEG1ControllerAttackInfluence); addProp(eEG1ControllerDecayInfluence); addProp(eEG1ControllerReleaseInfluence); + addLine(eEG1StateOptions); nextPage(); @@ -340,6 +621,21 @@ addProp(eCrossfade_out_start); addProp(eCrossfade_out_end); + Gtk::Frame* frame = new Gtk::Frame; + frame->add(crossfade_curve); + table[pageno]->attach(*frame, 1, 3, rowno, rowno + 1, + Gtk::SHRINK, Gtk::SHRINK); + rowno++; + + eCrossfade_in_start.signal_value_changed().connect( + sigc::mem_fun(crossfade_curve, &CrossfadeCurve::queue_draw)); + eCrossfade_in_end.signal_value_changed().connect( + sigc::mem_fun(crossfade_curve, &CrossfadeCurve::queue_draw)); + eCrossfade_out_start.signal_value_changed().connect( + sigc::mem_fun(crossfade_curve, &CrossfadeCurve::queue_draw)); + eCrossfade_out_end.signal_value_changed().connect( + sigc::mem_fun(crossfade_curve, &CrossfadeCurve::queue_draw)); + nextPage(); addHeader(_("General Filter Settings")); @@ -390,6 +686,22 @@ addProp(eVCFVelocityCurve); addProp(eVCFVelocityScale); addProp(eVCFVelocityDynamicRange); + + eVCFCutoffController.signal_value_changed().connect( + sigc::mem_fun(cutoff_curve, &VelocityCurve::queue_draw)); + eVCFVelocityCurve.signal_value_changed().connect( + sigc::mem_fun(cutoff_curve, &VelocityCurve::queue_draw)); + eVCFVelocityScale.signal_value_changed().connect( + sigc::mem_fun(cutoff_curve, &VelocityCurve::queue_draw)); + eVCFVelocityDynamicRange.signal_value_changed().connect( + sigc::mem_fun(cutoff_curve, &VelocityCurve::queue_draw)); + + frame = new Gtk::Frame; + frame->add(cutoff_curve); + table[pageno]->attach(*frame, 1, 3, rowno, rowno + 1, + Gtk::SHRINK, Gtk::SHRINK); + rowno++; + addProp(eVCFResonance); addProp(eVCFResonanceDynamic); { @@ -423,6 +735,7 @@ addProp(eEG2ControllerAttackInfluence); addProp(eEG2ControllerDecayInfluence); addProp(eEG2ControllerReleaseInfluence); + addLine(eEG2StateOptions); lLFO2 = addHeader(_("Filter Cutoff Oscillator (LFO2)")); addProp(eLFO2Frequency); addProp(eLFO2InternalDepth); @@ -472,14 +785,41 @@ nextPage(); + addHeader(_("Velocity Response")); eVelocityResponseCurve.set_choices(curve_type_texts, curve_type_values); addProp(eVelocityResponseCurve); addProp(eVelocityResponseDepth); addProp(eVelocityResponseCurveScaling); + + eVelocityResponseCurve.signal_value_changed().connect( + sigc::mem_fun(velocity_curve, &VelocityCurve::queue_draw)); + eVelocityResponseDepth.signal_value_changed().connect( + sigc::mem_fun(velocity_curve, &VelocityCurve::queue_draw)); + eVelocityResponseCurveScaling.signal_value_changed().connect( + sigc::mem_fun(velocity_curve, &VelocityCurve::queue_draw)); + + frame = new Gtk::Frame; + frame->add(velocity_curve); + table[pageno]->attach(*frame, 1, 3, rowno, rowno + 1, + Gtk::SHRINK, Gtk::SHRINK); + rowno++; + + addHeader(_("Release Velocity Response")); eReleaseVelocityResponseCurve.set_choices(curve_type_texts, curve_type_values); addProp(eReleaseVelocityResponseCurve); addProp(eReleaseVelocityResponseDepth); + + eReleaseVelocityResponseCurve.signal_value_changed().connect( + sigc::mem_fun(release_curve, &VelocityCurve::queue_draw)); + eReleaseVelocityResponseDepth.signal_value_changed().connect( + sigc::mem_fun(release_curve, &VelocityCurve::queue_draw)); + frame = new Gtk::Frame; + frame->add(release_curve); + table[pageno]->attach(*frame, 1, 3, rowno, rowno + 1, + Gtk::SHRINK, Gtk::SHRINK); + rowno++; + addProp(eReleaseTriggerDecay); { const char* choices[] = { _("none"), _("effect4depth"), _("effect5depth"), 0 }; @@ -491,8 +831,24 @@ eDimensionBypass.set_choices(choices, values); } addProp(eDimensionBypass); + eSelfMask.widget.set_tooltip_text(_( + "If enabled: new notes with higher velocity value will stop older " + "notes with lower velocity values, that way you can save voices that " + "would barely be audible. This is also useful for certain drum sounds." + )); addProp(eSelfMask); + eSustainDefeat.widget.set_tooltip_text(_( + "If enabled: sustain pedal will not hold a note. This way you can use " + "the sustain pedal for other purposes, for example to switch among " + "dimension regions." + )); addProp(eSustainDefeat); + eMSDecode.widget.set_tooltip_text(_( + "Defines if Mid/Side Recordings should be decoded. Mid/Side Recordings " + "are an alternative way to record sounds in stereo. The sampler needs " + "to decode such samples to actually make use of them. Note: this " + "feature is currently not supported by LinuxSampler." + )); addProp(eMSDecode); nextPage(); @@ -556,7 +912,7 @@ Gtk::Entry*& widget) { label = new Gtk::Label(Glib::ustring(labelText) + ":"); - label->set_alignment(Gtk::ALIGN_LEFT); + label->set_alignment(Gtk::ALIGN_START); table[pageno]->attach(*label, 1, 2, rowno, rowno + 1, Gtk::FILL, Gtk::SHRINK); @@ -569,6 +925,28 @@ rowno++; } +void DimRegionEdit::addString(const char* labelText, Gtk::Label*& label, + Gtk::Entry*& widget, Gtk::Button*& button) +{ + label = new Gtk::Label(Glib::ustring(labelText) + ":"); + label->set_alignment(Gtk::ALIGN_START); + + table[pageno]->attach(*label, 1, 2, rowno, rowno + 1, + Gtk::FILL, Gtk::SHRINK); + + widget = new Gtk::Entry(); + button = new Gtk::Button(); + + Gtk::HBox* hbox = new Gtk::HBox; + hbox->pack_start(*widget); + hbox->pack_start(*button, Gtk::PACK_SHRINK); + + table[pageno]->attach(*hbox, 2, 3, rowno, rowno + 1, + Gtk::EXPAND | Gtk::FILL, Gtk::SHRINK); + + rowno++; +} + Gtk::Label* DimRegionEdit::addHeader(const char* text) { if (firstRowInBlock < rowno - 1) @@ -582,7 +960,7 @@ str += ""; Gtk::Label* label = new Gtk::Label(str); label->set_use_markup(); - label->set_alignment(Gtk::ALIGN_LEFT); + label->set_alignment(Gtk::ALIGN_START); table[pageno]->attach(*label, 0, 3, rowno, rowno + 1, Gtk::FILL, Gtk::SHRINK); rowno++; @@ -626,10 +1004,27 @@ rowno++; } +void DimRegionEdit::addLine(Gtk::HBox& line) +{ + table[pageno]->attach(line, 1, 3, rowno, rowno + 1, + Gtk::FILL, Gtk::SHRINK); + rowno++; +} + +void DimRegionEdit::addRightHandSide(Gtk::Widget& widget) +{ + table[pageno]->attach(widget, 2, 3, rowno, rowno + 1, + Gtk::EXPAND | Gtk::FILL, Gtk::SHRINK); + rowno++; +} void DimRegionEdit::set_dim_region(gig::DimensionRegion* d) { dimregion = d; + velocity_curve.set_dim_region(d); + release_curve.set_dim_region(d); + cutoff_curve.set_dim_region(d); + crossfade_curve.set_dim_region(d); set_sensitive(d); if (!d) return; @@ -648,6 +1043,11 @@ eEG1ControllerAttackInfluence.set_value(d->EG1ControllerAttackInfluence); eEG1ControllerDecayInfluence.set_value(d->EG1ControllerDecayInfluence); eEG1ControllerReleaseInfluence.set_value(d->EG1ControllerReleaseInfluence); + eEG1StateOptions.checkBoxAttack.set_value(d->EG1Options.AttackCancel); + eEG1StateOptions.checkBoxAttackHold.set_value(d->EG1Options.AttackHoldCancel); + eEG1StateOptions.checkBoxDecay1.set_value(d->EG1Options.Decay1Cancel); + eEG1StateOptions.checkBoxDecay2.set_value(d->EG1Options.Decay2Cancel); + eEG1StateOptions.checkBoxRelease.set_value(d->EG1Options.ReleaseCancel); eLFO1Frequency.set_value(d->LFO1Frequency); eLFO1InternalDepth.set_value(d->LFO1InternalDepth); eLFO1ControlDepth.set_value(d->LFO1ControlDepth); @@ -666,6 +1066,11 @@ eEG2ControllerAttackInfluence.set_value(d->EG2ControllerAttackInfluence); eEG2ControllerDecayInfluence.set_value(d->EG2ControllerDecayInfluence); eEG2ControllerReleaseInfluence.set_value(d->EG2ControllerReleaseInfluence); + eEG2StateOptions.checkBoxAttack.set_value(d->EG2Options.AttackCancel); + eEG2StateOptions.checkBoxAttackHold.set_value(d->EG2Options.AttackHoldCancel); + eEG2StateOptions.checkBoxDecay1.set_value(d->EG2Options.Decay1Cancel); + eEG2StateOptions.checkBoxDecay2.set_value(d->EG2Options.Decay2Cancel); + eEG2StateOptions.checkBoxRelease.set_value(d->EG2Options.ReleaseCancel); eLFO2Frequency.set_value(d->LFO2Frequency); eLFO2InternalDepth.set_value(d->LFO2InternalDepth); eLFO2ControlDepth.set_value(d->LFO2ControlDepth); @@ -714,6 +1119,55 @@ eMSDecode.set_value(d->MSDecode); eSampleStartOffset.set_value(d->SampleStartOffset); eUnityNote.set_value(d->UnityNote); + // show sample group name + { + Glib::ustring s = "---"; + if (d->pSample && d->pSample->GetGroup()) + s = d->pSample->GetGroup()->Name; + eSampleGroup.text.set_text(s); + } + // assemble sample format info string + { + Glib::ustring s; + if (d->pSample) { + switch (d->pSample->Channels) { + case 1: s = _("Mono"); break; + case 2: s = _("Stereo"); break; + default: + s = ToString(d->pSample->Channels) + _(" audio channels"); + break; + } + s += " " + ToString(d->pSample->BitDepth) + " Bits"; + s += " " + ToString(d->pSample->SamplesPerSecond/1000) + "." + + ToString((d->pSample->SamplesPerSecond%1000)/100) + " kHz"; + } else { + s = _("No sample assigned to this dimension region."); + } + eSampleFormatInfo.text.set_text(s); + } + // generate sample's memory address pointer string + { + Glib::ustring s; + if (d->pSample) { + char buf[64] = {}; + snprintf(buf, sizeof(buf), "%p", d->pSample); + s = buf; + } else { + s = "---"; + } + eSampleID.text.set_text(s); + } + // generate raw wave form data CRC-32 checksum string + { + Glib::ustring s = "---"; + if (d->pSample) { + char buf[64] = {}; + snprintf(buf, sizeof(buf), "%x", d->pSample->GetWaveDataCRC32Checksum()); + s = buf; + } + eChecksum.text.set_text(s); + } + buttonSelectSample.set_sensitive(d && d->pSample); eFineTune.set_value(d->FineTune); eGain.set_value(d->Gain); eGainPlus6.set_value(d->Gain); @@ -730,7 +1184,8 @@ d->pSample ? d->pSample->LoopPlayCount : 0); update_model--; - wSample->set_text(d->pSample ? d->pSample->pInfo->Name.c_str() : _("NULL")); + wSample->set_text(d->pSample ? gig_to_utf8(d->pSample->pInfo->Name) : + _("NULL")); update_loop_elements(); VCFEnabled_toggled(); @@ -745,6 +1200,7 @@ eVCFVelocityCurve.set_sensitive(sensitive); eVCFVelocityScale.set_sensitive(sensitive); eVCFVelocityDynamicRange.set_sensitive(sensitive); + cutoff_curve.set_sensitive(sensitive); eVCFResonance.set_sensitive(sensitive); eVCFResonanceController.set_sensitive(sensitive); eVCFKeyboardTracking.set_sensitive(sensitive); @@ -760,6 +1216,7 @@ eEG2ControllerAttackInfluence.set_sensitive(sensitive); eEG2ControllerDecayInfluence.set_sensitive(sensitive); eEG2ControllerReleaseInfluence.set_sensitive(sensitive); + eEG2StateOptions.set_sensitive(sensitive); lLFO2->set_sensitive(sensitive); eLFO2Frequency.set_sensitive(sensitive); eLFO2InternalDepth.set_sensitive(sensitive); @@ -837,6 +1294,7 @@ eCrossfade_in_end.set_sensitive(hasController); eCrossfade_out_start.set_sensitive(hasController); eCrossfade_out_end.set_sensitive(hasController); + crossfade_curve.set_sensitive(hasController); } void DimRegionEdit::LFO1Controller_changed() @@ -950,20 +1408,31 @@ update_model--; } -bool DimRegionEdit::set_sample(gig::Sample* sample) +bool DimRegionEdit::set_sample(gig::Sample* sample, bool copy_sample_unity, bool copy_sample_tune, bool copy_sample_loop) +{ + bool result = false; + for (std::set::iterator itDimReg = dimregs.begin(); + itDimReg != dimregs.end(); ++itDimReg) + { + result |= set_sample(*itDimReg, sample, copy_sample_unity, copy_sample_tune, copy_sample_loop); + } + return result; +} + +bool DimRegionEdit::set_sample(gig::DimensionRegion* dimreg, gig::Sample* sample, bool copy_sample_unity, bool copy_sample_tune, bool copy_sample_loop) { - if (dimregion) { + if (dimreg) { //TODO: we should better move the code from MainWindow::on_sample_label_drop_drag_data_received() here // currently commented because we're sending a similar signal in MainWindow::on_sample_label_drop_drag_data_received() - //dimreg_to_be_changed_signal.emit(dimregion); + //DimRegionChangeGuard(this, dimregion); // make sure stereo samples always are the same in both // dimregs in the samplechannel dimension int nbDimregs = 1; - gig::DimensionRegion* d[2] = { dimregion, 0 }; + gig::DimensionRegion* d[2] = { dimreg, 0 }; if (sample->Channels == 2) { - gig::Region* region = dimregion->GetParent(); + gig::Region* region = dimreg->GetParent(); int bitcount = 0; int stereo_bit = 0; @@ -978,7 +1447,7 @@ if (stereo_bit) { int dimregno; for (dimregno = 0 ; dimregno < region->DimensionRegions ; dimregno++) { - if (region->pDimensionRegions[dimregno] == dimregion) { + if (region->pDimensionRegions[dimregno] == dimreg) { break; } } @@ -988,44 +1457,44 @@ } } - gig::Sample* oldref = dimregion->pSample; + gig::Sample* oldref = dimreg->pSample; for (int i = 0 ; i < nbDimregs ; i++) { d[i]->pSample = sample; // copy sample information from Sample to DimensionRegion - - d[i]->UnityNote = sample->MIDIUnityNote; - d[i]->FineTune = sample->FineTune; - - int loops = sample->Loops ? 1 : 0; - while (d[i]->SampleLoops > loops) { - d[i]->DeleteSampleLoop(&d[i]->pSampleLoops[0]); - } - while (d[i]->SampleLoops < sample->Loops) { - DLS::sample_loop_t loop; - d[i]->AddSampleLoop(&loop); - } - if (loops) { - d[i]->pSampleLoops[0].Size = sizeof(DLS::sample_loop_t); - d[i]->pSampleLoops[0].LoopType = sample->LoopType; - d[i]->pSampleLoops[0].LoopStart = sample->LoopStart; - d[i]->pSampleLoops[0].LoopLength = sample->LoopEnd - sample->LoopStart + 1; + if (copy_sample_unity) + d[i]->UnityNote = sample->MIDIUnityNote; + if (copy_sample_tune) + d[i]->FineTune = sample->FineTune; + if (copy_sample_loop) { + int loops = sample->Loops ? 1 : 0; + while (d[i]->SampleLoops > loops) { + d[i]->DeleteSampleLoop(&d[i]->pSampleLoops[0]); + } + while (d[i]->SampleLoops < sample->Loops) { + DLS::sample_loop_t loop; + d[i]->AddSampleLoop(&loop); + } + if (loops) { + d[i]->pSampleLoops[0].Size = sizeof(DLS::sample_loop_t); + d[i]->pSampleLoops[0].LoopType = sample->LoopType; + d[i]->pSampleLoops[0].LoopStart = sample->LoopStart; + d[i]->pSampleLoops[0].LoopLength = sample->LoopEnd - sample->LoopStart + 1; + } } } // update ui update_model++; - wSample->set_text(dimregion->pSample->pInfo->Name); - eUnityNote.set_value(dimregion->UnityNote); - eFineTune.set_value(dimregion->FineTune); - eSampleLoopEnabled.set_value(dimregion->SampleLoops); + wSample->set_text(gig_to_utf8(dimreg->pSample->pInfo->Name)); + eUnityNote.set_value(dimreg->UnityNote); + eFineTune.set_value(dimreg->FineTune); + eSampleLoopEnabled.set_value(dimreg->SampleLoops); update_loop_elements(); update_model--; sample_ref_changed_signal.emit(oldref, sample); - // currently commented because we're sending a similar signal in MainWindow::on_sample_label_drop_drag_data_received() - //dimreg_changed_signal.emit(dimregion); return true; } return false; @@ -1094,24 +1563,24 @@ if (value) { // create a new sample loop in case there is none yet if (!d->SampleLoops) { + DimRegionChangeGuard(this, d); + DLS::sample_loop_t loop; loop.LoopType = gig::loop_type_normal; // loop the whole sample by default loop.LoopStart = 0; loop.LoopLength = (d->pSample) ? d->pSample->SamplesTotal : 0; - dimreg_to_be_changed_signal.emit(d); d->AddSampleLoop(&loop); - dimreg_changed_signal.emit(d); } } else { if (d->SampleLoops) { - dimreg_to_be_changed_signal.emit(d); + DimRegionChangeGuard(this, d); + // delete ALL existing sample loops while (d->SampleLoops) { d->DeleteSampleLoop(&d->pSampleLoops[0]); } - dimreg_changed_signal.emit(d); } } } @@ -1155,3 +1624,58 @@ { if (d->pSample) d->pSample->LoopPlayCount = value; } + +void DimRegionEdit::nullOutSampleReference() { + if (!dimregion) return; + gig::Sample* oldref = dimregion->pSample; + if (!oldref) return; + + DimRegionChangeGuard(this, dimregion); + + // in case currently assigned sample is a stereo one, then remove both + // references (expected to be due to a "stereo dimension") + gig::DimensionRegion* d[2] = { dimregion, NULL }; + if (oldref->Channels == 2) { + gig::Region* region = dimregion->GetParent(); + { + int stereo_bit = 0; + int bitcount = 0; + for (int dim = 0 ; dim < region->Dimensions ; dim++) { + if (region->pDimensionDefinitions[dim].dimension == gig::dimension_samplechannel) { + stereo_bit = 1 << bitcount; + break; + } + bitcount += region->pDimensionDefinitions[dim].bits; + } + + if (stereo_bit) { + int dimregno; + for (dimregno = 0 ; dimregno < region->DimensionRegions ; dimregno++) { + if (region->pDimensionRegions[dimregno] == dimregion) { + break; + } + } + d[0] = region->pDimensionRegions[dimregno & ~stereo_bit]; + d[1] = region->pDimensionRegions[dimregno | stereo_bit]; + } + } + } + + if (d[0]) d[0]->pSample = NULL; + if (d[1]) d[1]->pSample = NULL; + + // update UI elements + set_dim_region(dimregion); + + sample_ref_changed_signal.emit(oldref, NULL); +} + +void DimRegionEdit::onButtonSelectSamplePressed() { + if (!dimregion) return; + if (!dimregion->pSample) return; + select_sample_signal.emit(dimregion->pSample); +} + +sigc::signal& DimRegionEdit::signal_select_sample() { + return select_sample_signal; +}