diff --git a/CHANGELOG.md b/CHANGELOG.md index 1492387..f912414 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,21 +1,54 @@ # SuperDiffuse Change Log ## Known issues -* Moving control faders (MIDI) while a piece is loading will lock the interface, forcing a restart of SuperDiffuse. This appears to be a SuperCollider/Qt issue that can't really be addressed +- [1] Moving control faders (MIDI) while a piece is loading will lock the interface, forcing a restart of SuperDiffuse. This appears to be a SuperCollider/Qt threading issue. + * This may have been addressed in newer SC versions(?) +- [2] Using the mouse-wheel in the Edit Matrix window when it has scrollbars can cause trouble. The NumberBox doesn't steal the mouse properly on mouse-wheel, so the scroll event propagates to the ScrollArea. I'm working on a PR to the main SuperCollider repo to address this. + +## Version 1.4.0 +A big usability update. This version __requires SuperCollider >= 3.9__. + +Core functionality is the same, but lots of new features to make things easier. Thanks to everyone who has used the system so far and provided invaluable feedback. In particular, this version features additions requested by Adrian Moore, Adam Stanovic, Hans Tutsku, and David Berezan; thank you all. + +### New features +* Added meter bridge to GUI (post-master fader) + * __NOTE__: requires SuperCollider >= 3.9.3 to function +* Added Ctrl+D for duplicating Matrices. +* Added Ctrl+D for duplicating FilterSets. +* Added auto-save. If you've saved the concert once (or loaded it), any changes that would require a save now automatically trigger a resave: + * Adding/Removing/Swapping/Editing Pieces + * Adding/Removing/Editing Matrices + * Adding/Removing/Editing Filter Sets + * Setting Master Fader + * Setting Control Faders + * Setting MIDI Config +* Added 'Hide Waveform' button. This removes the waveform and the playhead from the soundfile view, leaving only a black box. When hidden, the soundfile view doesn't respond to mouse clicks, so you can't change the playback position while hidden (keyboard still responds, so start/stop and returning to beginning remain possible). + +### Changes +* Changed Master Fader to behave exponentially (like control faders). The numberbox values are linear, but they scale exponentially - beware values above 1! +* Lock Interface now disables mouse interaction with the Soundfile View, offering a more coherent 'concert mode'. +* Lock Interface now requires two clicks to unlock, providing additional safety for concerts. +* GUI code refactor - the layout is now grid-based and compartmentalised. Apart from being easier to maintain, this means scaling is handled better. Widgets can also now be smaller than before, reducing screen real-estate requirements. +* The Pieces add/remove and up/down buttons have been swapped to mirror usual workflow: adding/removing pieces, _then_ swapping order. +* The main window title now includes the filename of the currently loaded concert. + +### Fixes +* Fixed a bug where selecting a Matrix and pressing play would start the piece at the matrix index +* Fixed an issue with the Clock displaying decimals on newer SuperCollider versions (backwards compatible) +* Reduced the number of calls to ControlFader GUI updates on value changes (might help with Known Issue [1]) ## Version 1.3.0 - +### New Features * Added configurable input (pre-routing matrix) and output (post-routing matrix) filters ## Version 1.2.0 - +### Changes * Changed faders to behave uniformly (regardless of control from MIDI, OSC or GUI) * Added GPLv3 license ## Version 1.1.1 - +### Fixes * Minor patch to fix issues with machine specific keyboard shortcuts (now uses Qt keycodes) ## Version 1.1.0 - * First public release. Thanks to Adrian Moore, Adam Stanovic, Jonty Harrison, Denis Smalley, and the composers at USSS for their support and invaluable thoughts diff --git a/Classes/Components/FilterSystem/SuperDiffuse_FilterSet.sc b/Classes/Components/FilterSystem/SuperDiffuse_FilterSet.sc index 4f9d53b..bbfad4e 100644 --- a/Classes/Components/FilterSystem/SuperDiffuse_FilterSet.sc +++ b/Classes/Components/FilterSystem/SuperDiffuse_FilterSet.sc @@ -20,12 +20,22 @@ SuperDiffuse_FilterSet ^super.new.init(numIns, numOuts, inBus, outBus, inFxGroup, outFxGroup); } + *newFrom { | filterSet, name | + ^super.new.initFrom(filterSet, name); + } + init { | numIns, numOuts, inBus, outBus, inFxGroup, outFxGroup | m_inFilters = Array.fill(numIns, { | ind | SuperDiffuse_FilterUnit(inBus.subBus(ind), inFxGroup ); }); m_outFilters = Array.fill(numOuts, { | ind | SuperDiffuse_FilterUnit(outBus.subBus(ind), outFxGroup); }); m_name = "New Filter Set"; } + initFrom { | filterSet, name | + m_name = name; + m_inFilters = filterSet.inFilters.deepCopy.do({ | filterUnit |filterUnit.init; }); + m_outFilters = filterSet.outFilters.deepCopy.do({ | filterUnit | filterUnit.init; }); + } + inFilterAt { | ind | ^m_inFilters[ind]; } diff --git a/Classes/Components/FilterSystem/SuperDiffuse_FilterSetManager.sc b/Classes/Components/FilterSystem/SuperDiffuse_FilterSetManager.sc index 65de2b8..295aee7 100644 --- a/Classes/Components/FilterSystem/SuperDiffuse_FilterSetManager.sc +++ b/Classes/Components/FilterSystem/SuperDiffuse_FilterSetManager.sc @@ -45,16 +45,19 @@ SuperDiffuse_FilterSetManager } removeFilterSet { | ind | - // if we're removing the current set, unload it first - if(m_currentSet != nil) + if(ind > 0) // don't remove the default filterset { - if(m_filterSets[ind] === m_currentSet) + // if we're removing the current set, unload it first + if(m_currentSet != nil) { - this.unload; + if(m_filterSets[ind] === m_currentSet) + { + this.unload; + }; }; - }; - m_filterSets.removeAt(ind); + m_filterSets.removeAt(ind); + } } at { | ind | diff --git a/Classes/Components/SuperDiffuse_Clock.sc b/Classes/Components/SuperDiffuse_Clock.sc index eed137b..d255d3a 100644 --- a/Classes/Components/SuperDiffuse_Clock.sc +++ b/Classes/Components/SuperDiffuse_Clock.sc @@ -52,7 +52,7 @@ SuperDiffuse_Clock m = (totalSeconds / 60 % 60).floor; h = (totalSeconds / 60 / 60).floor; - ^"%:%:%".format(h.asString.padLeft(2,"0"),m.asString.padLeft(2,"0"),s.asString.padLeft(2,"0")); + ^"%:%:%".format(h.asInteger.asString.padLeft(2,"0"),m.asInteger.asString.padLeft(2,"0"),s.asInteger.asString.padLeft(2,"0")); } reset { @@ -72,4 +72,4 @@ SuperDiffuse_Clock updateGUI { {display.string_(this.getString)}.defer; } -} \ No newline at end of file +} diff --git a/Classes/Components/SuperDiffuse_ConcertGUI.sc b/Classes/Components/SuperDiffuse_ConcertGUI.sc index e0084ea..96445e2 100644 --- a/Classes/Components/SuperDiffuse_ConcertGUI.sc +++ b/Classes/Components/SuperDiffuse_ConcertGUI.sc @@ -1,7 +1,11 @@ SuperDiffuse_ConcertGUI : SuperDiffuse_Observer { var m_parent; - var m_win, m_mainLayout, m_topLayout, m_leftLayout, m_piecesLayout, m_piecesButtonLayout, m_matricesLayout, m_matricesButtonLayout, m_rightLayout, m_playbackControlsLayout; + var m_win, m_layout; + + var m_meterBridge; + + var m_firstPiece; var m_piecesListView, m_pieceEditFunc, m_piecesUpButton, m_piecesDownButton, m_piecesAddButton, m_piecesRemoveButton; @@ -14,17 +18,27 @@ SuperDiffuse_ConcertGUI : SuperDiffuse_Observer { var m_clock; var m_sfView; - //var m_backButton, m_playStopButton, m_forwardButton; var m_playheadRoutine; + var m_locked, m_sfViewHidden; + *new { | parent | ^super.new(parent).ninit(parent); } ninit { | parent | m_parent = parent; + m_firstPiece = true; + m_locked = false; + m_sfViewHidden = false; + this.initWindow; this.update; + + if(m_parent.pieces.size > 0) + { + this.loadFirstPiece; + }; } initWindow { @@ -38,24 +52,50 @@ SuperDiffuse_ConcertGUI : SuperDiffuse_Observer { winY = (screenHeight / 2 ) - (winHeight / 2); - m_win = Window("SuperDiffuse | v.1.3.0", Rect(winX,winY,winWidth,winHeight)); - m_mainLayout = VLayout(); - m_topLayout = HLayout(); - m_leftLayout = HLayout(); - m_piecesLayout = VLayout(); - m_matricesLayout = VLayout(); - m_rightLayout = VLayout(); + m_win = Window("SuperDiffuse | v" ++ SuperDiffuse.version, Rect(winX,winY,winWidth,winHeight)); + + m_layout = GridLayout(); + + this.prCreatePiecesView; + this.prCreateMatricesView; + this.prCreateControlsView; + this.prCreateSfView; - m_piecesButtonLayout = HLayout(); - m_matricesButtonLayout = HLayout(); - m_playbackControlsLayout = HLayout(); + m_layout.setColumnStretch(0, 0); + m_layout.setColumnStretch(1, 0); + m_layout.setColumnStretch(2, 1); - m_win.layout_(m_mainLayout); + m_layout.setRowStretch(0, 0); + m_layout.setRowStretch(1, 0); + m_layout.setRowStretch(2, 0); + m_layout.setRowStretch(3, 1); + + m_win.view.keyDownAction_({ | caller, modifiers, unicode, keycode | + case + {keycode == 32} { if(m_parent.isPlaying) { this.stop } { this.play }; } + {keycode == 13} { m_sfView.timeCursorPosition_(0); m_clock.reset; m_sfView.setSelection(0,[0,0]); } + }); + + m_win.onClose_({ + this.stop; + m_parent.clear; + if(m_meterBridge.notNil) + { + m_meterBridge.free; + }; + }); + + m_win.layout_(m_layout); + + m_win.front; + } + + prCreatePiecesView { m_pieceEditFunc = { | caller, char, modifiers, unicode, keycode, key | if(caller.hasFocus) { - if( (caller.selection[0] != nil) && (key == 0x45) && (modifiers.isCtrl || modifiers.isCmd)) + if( (caller.selection[0] != nil) && (key == 0x45) && (modifiers.isCtrl || modifiers.isCmd) && (m_locked == false)) { var win, layout, nameLayout, textEdit, buttonLayout, okButton, cancelButton, matrixLayout, matrixMenu, filterLayout, filterMenu; var sel, piece; @@ -96,6 +136,7 @@ SuperDiffuse_ConcertGUI : SuperDiffuse_Observer { m_matricesListView.valueAction_(piece.matrixInd); this.updateMatrices; win.close; + m_parent.createSaveFile(m_parent.saveFileLoc); }); buttonLayout.add(cancelButton); @@ -108,35 +149,23 @@ SuperDiffuse_ConcertGUI : SuperDiffuse_Observer { win.layout_(layout); win.front; - }; - if( (caller.selection[0] != nil) && (key == 0x20) ) + } { - if(m_parent.isPlaying) + if( (caller.selection[0] != nil) && (key == 0x20) ) { - this.stop; + if(m_parent.isPlaying) + { + this.stop; + } + { + this.play(caller.selection[0]); + }; } - { - this.play(caller.selection[0]); - }; } }; }; - m_masterVolumeNumberBox = NumberBox().maxWidth_(50).action_({ | caller | - m_parent.setMasterLevel(caller.value); - m_masterVolumeSlider.value_(caller.value); - if(m_piecesListView.selection[0] != nil) - { - m_parent.pieces[m_piecesListView.selection[0]].masterLevel_(caller.value); - } - }); - - m_masterVolumeSlider = Slider().orientation_(\horizontal).action_({ | caller | - m_masterVolumeNumberBox.valueAction_(caller.value); - }); - m_piecesListView = ListView().action_({ | lv | - this.updateSFView; this.stop; m_matricesListView.valueAction_(m_parent.pieces[lv.selection[0]].matrixInd); @@ -146,38 +175,15 @@ SuperDiffuse_ConcertGUI : SuperDiffuse_Observer { m_parent.filterManager.load(m_parent.pieces[lv.selection[0]].filterInd); }; + this.updateSFView; m_sfView.setSelection(0, [0,0]); m_sfView.timeCursorPosition_(0); m_clock.reset; + lv.focus; }) .keyDownAction_(m_pieceEditFunc); - m_piecesLayout.add(StaticText().string_("Pieces"),align:\center); - m_piecesLayout.add(m_piecesListView); - - m_piecesUpButton = Button().states_([["^"]]).action_({ | btn | - var sel; - sel = m_piecesListView.selection[0]; - if( (sel != 0) && (sel != nil) ) - { - m_parent.pieces.swap(sel,sel-1); - this.updatePieces; - m_piecesListView.selection = sel - 1; - }; - }); - - m_piecesDownButton = Button().states_([["v"]]).action_({ | btn | - var sel; - sel = m_piecesListView.selection[0]; - if( (sel != (m_parent.pieces.size-1)) && (sel != nil) ) - { - m_parent.pieces.swap(sel,sel + 1); - this.updatePieces; - m_piecesListView.value = sel + 1; - }; - }); - - m_piecesAddButton = Button().states_([["+"]]).action_({ + m_piecesAddButton = Button().minWidth_(10).states_([["+"]]).action_({ Dialog.openPanel({|v| var sizeBefore, sizeAfter; @@ -195,152 +201,208 @@ SuperDiffuse_ConcertGUI : SuperDiffuse_Observer { m_piecesListView.valueAction_(0); }; },multipleSelection:true); + m_parent.createSaveFile(m_parent.saveFileLoc); + + m_piecesListView.focus; }); - m_piecesRemoveButton = Button().states_([["-"]]).action_({ + + m_piecesRemoveButton = Button().minWidth_(10).states_([["-"]]).action_({ var sel; sel = m_piecesListView.selection[0]; if(sel != nil) { m_parent.removePiece(m_parent.pieces[sel]); + if(sel != 0) { m_piecesListView.valueAction_(sel - 1); - } + }; + + m_parent.createSaveFile(m_parent.saveFileLoc); }; + + m_piecesListView.focus; }); - m_piecesButtonLayout.add(m_piecesUpButton); - m_piecesButtonLayout.add(m_piecesDownButton); - m_piecesButtonLayout.add(m_piecesAddButton); - m_piecesButtonLayout.add(m_piecesRemoveButton); + m_piecesUpButton = Button().minWidth_(10).states_([["^"]]).action_({ | btn | + var sel; + sel = m_piecesListView.selection[0]; + if( (sel != 0) && (sel != nil) ) + { + m_parent.pieces.swap(sel,sel-1); + this.updatePieces; + m_piecesListView.selection = sel - 1; + m_parent.createSaveFile(m_parent.saveFileLoc); + }; + + m_piecesListView.focus; + }); + + m_piecesDownButton = Button().minWidth_(10).states_([["v"]]).action_({ | btn | + var sel; + sel = m_piecesListView.selection[0]; + if( (sel != (m_parent.pieces.size-1)) && (sel != nil) ) + { + m_parent.pieces.swap(sel,sel + 1); + this.updatePieces; + m_piecesListView.value = sel + 1; + m_parent.createSaveFile(m_parent.saveFileLoc); + }; - m_piecesLayout.add(m_piecesButtonLayout); + m_piecesListView.focus; + }); - m_leftLayout.add(m_piecesLayout); + m_layout.addSpanning( + VLayout( + StaticText().string_("Pieces").align_(\center), + m_piecesListView, + HLayout( + m_piecesAddButton, + m_piecesRemoveButton, + m_piecesUpButton, + m_piecesDownButton, + ) + ), + row:0, + column:0, + rowSpan:4, + columnSpan:1 + ); + } + prCreateMatricesView { m_matrixEditFunc = { | caller, char, modifiers, unicode, keycode, key | if(caller.hasFocus) { - if( (caller.selection[0] != nil) && (key == 0x45) && (modifiers.isCtrl || modifiers.isCmd)) + var selectedMatrix = caller.selection[0]; + + if(selectedMatrix != nil) { - var win, layout, fieldLayout, textEdit, buttonLayout, matrixLayout, okButton, cancelButton, matrixScrollView, matrixScrollCanvas; - var sel, matrix; - var tmpMatrix; + case + { key == 0x20 } { + if(m_parent.isPlaying) + { + this.stop; + } + { + if(m_piecesListView.selection[0] != nil) + { + this.play(m_piecesListView.selection[0]); + } + }; + } + { (key == 0x45) && (modifiers.isCtrl || modifiers.isCmd) && (m_locked == false) } { + var win, layout, fieldLayout, textEdit, buttonLayout, matrixLayout, okButton, cancelButton, matrixScrollView, matrixScrollCanvas; + var sel, matrix; + var tmpMatrix; - sel = caller.selection[0]; - matrix = m_parent.matrix(sel); - tmpMatrix = SuperDiffuse_Matrix.newFrom(matrix,"tmp"); + sel = caller.selection[0]; + matrix = m_parent.matrix(sel); + tmpMatrix = SuperDiffuse_Matrix.newFrom(matrix,"tmp"); - win = Window("SuperDiffuse | Edit Matrix"); - layout = VLayout(); - fieldLayout = HLayout(); - buttonLayout = HLayout(); + win = Window("SuperDiffuse | Edit Matrix"); + layout = VLayout(); + fieldLayout = HLayout(); + buttonLayout = HLayout(); - fieldLayout.add(StaticText().string_("Name: ")); - textEdit = TextField().string_(matrix.name); - fieldLayout.add(textEdit); + fieldLayout.add(StaticText().string_("Name: ")); + textEdit = TextField().string_(matrix.name); + fieldLayout.add(textEdit); - cancelButton = Button().states_([["Cancel"]]).action_({ - win.close; - }); - okButton = Button().states_([["OK"]]).action_({ - if(textEdit.string != matrix.name) - { - matrix.name = textEdit.string; + cancelButton = Button().states_([["Cancel"]]).action_({ + win.close; + }); + okButton = Button().states_([["OK"]]).action_({ + if(textEdit.string != matrix.name) + { + matrix.name = textEdit.string; - caller.value_(sel); + caller.value_(sel); - }; - matrix.matrix = tmpMatrix.matrix; - m_parent.loadMatrix(matrix); + }; + matrix.matrix = tmpMatrix.matrix; + m_parent.loadMatrix(matrix); - this.updateMatrices; + this.updateMatrices; - win.close; - }); + m_parent.createSaveFile(m_parent.saveFileLoc); + win.close; + }); - buttonLayout.add(cancelButton); - buttonLayout.add(okButton); + buttonLayout.add(cancelButton); + buttonLayout.add(okButton); - layout.add(fieldLayout); + layout.add(fieldLayout); - matrixScrollView = ScrollView(); - matrixScrollCanvas = View(); - matrixScrollView.canvas_(matrixScrollCanvas); + matrixScrollView = ScrollView(); + matrixScrollCanvas = View(); + matrixScrollView.canvas_(matrixScrollCanvas); - matrixLayout = GridLayout(); - matrixScrollCanvas.layout_(matrixLayout); + matrixLayout = GridLayout(); + matrixScrollCanvas.layout_(matrixLayout); - matrix.matrix.size.do({ | in | - matrix.matrix[0].size.do({ | out | - var numBox; + matrix.matrix.size.do({ | in | + matrix.matrix[0].size.do({ | out | + var numBox; - numBox = NumberBox() - .align_(\center) - .clipLo_(0) - .value_(tmpMatrix.matrix[in][out]) - .action_({|caller| - tmpMatrix.matrix[in][out] = caller.value; + numBox = NumberBox() + .align_(\center) + .clipLo_(0) + .value_(tmpMatrix.matrix[in][out]) + .action_({|caller| + tmpMatrix.matrix[in][out] = caller.value; - if(caller.value > 1) - { - caller.background_(Color.red); - } - { - if(caller.value > 0) + if(caller.value > 1) { - caller.background_(Color.green(1, caller.value)); + caller.background_(Color.red); } { - caller.background_(Color.white); + if(caller.value > 0) + { + caller.background_(Color.green(1, caller.value)); + } + { + caller.background_(Color.white); + }; }; - }; - }) - .toolTip_("%:%".format(in+1, out+1)); + }) + .toolTip_("%:%".format(in+1, out+1)); - if(tmpMatrix.matrix[in][out] > 1) - { - numBox.background_(Color.red); - } - { - if(tmpMatrix.matrix[in][out] > 0) + if(tmpMatrix.matrix[in][out] > 1) { - numBox.background_(Color.green(1,tmpMatrix.matrix[in][out])); + numBox.background_(Color.red); } { - numBox.background_(Color.white); + if(tmpMatrix.matrix[in][out] > 0) + { + numBox.background_(Color.green(1,tmpMatrix.matrix[in][out])); + } + { + numBox.background_(Color.white); + }; }; - }; - matrixLayout.add(StaticText().string_("In%".format(in+1)).align_(\center), in+1, 0); - matrixLayout.add(StaticText().string_("Out%".format(out+1)).align_(\center), 0, out+1); - matrixLayout.add(numBox,in+1,out+1); + matrixLayout.add(StaticText().string_("In%".format(in+1)).align_(\center), in+1, 0); + matrixLayout.add(StaticText().string_("Out%".format(out+1)).align_(\center), 0, out+1); + matrixLayout.add(numBox,in+1,out+1); + }); }); - }); - layout.add(matrixScrollView); + layout.add(matrixScrollView); - layout.add(buttonLayout); + layout.add(buttonLayout); - win.layout_(layout); - win.front; - }; - { - if( (caller.selection[0] != nil) && (key == 0x20)) - { - if(m_parent.isPlaying) - { - this.stop; - } - { - this.play(caller.selection[0]); - }; + win.layout_(layout); + win.front; } - } + { (key == 0x44) && (modifiers.isCtrl || modifiers.isCmd) } { + m_parent.addMatrix("copy", selectedMatrix); + }; + }; }; }; @@ -352,19 +414,16 @@ SuperDiffuse_ConcertGUI : SuperDiffuse_Observer { }) .keyDownAction_(m_matrixEditFunc); - m_matricesLayout.add(StaticText().string_("Matrices"),align:\center); - m_matricesLayout.add(m_matricesListView); - - m_matrixAddButton = Button().states_([["+"]]).action_({ + m_matrixAddButton = Button().minWidth_(10).states_([["+"]]).action_({ var sel; sel = m_matricesListView.selection[0]; m_parent.addMatrix("Untitled"); m_matricesListView.value_(sel); + m_parent.createSaveFile(m_parent.saveFileLoc); + m_matricesListView.focus; }); - m_matricesButtonLayout.add(m_matrixAddButton); - - m_matrixRemoveButton = Button().states_([["-"]]).action_({ + m_matrixRemoveButton = Button().minWidth_(10).states_([["-"]]).action_({ var sel; sel = m_matricesListView.selection[0]; @@ -380,25 +439,157 @@ SuperDiffuse_ConcertGUI : SuperDiffuse_Observer { { piece.matrixInd = 0; } - }) + }); + m_parent.createSaveFile(m_parent.saveFileLoc); } { "Unable to remove initial matrix".warn; } }; + m_matricesListView.focus; + }); + + m_layout.addSpanning( + VLayout( + StaticText().string_("Matrices").align_(\center), + m_matricesListView, + HLayout( + m_matrixAddButton, + m_matrixRemoveButton + ) + ), + row: 0, + column: 1, + rowSpan: 4, + columnSpan: 1 + ); + } + + prCreateControlsView { + m_masterVolumeNumberBox = NumberBox().maxWidth_(50).action_({ | caller | + m_parent.setMasterLevel(caller.value ** 2); + m_masterVolumeSlider.value_(caller.value); + + if(m_piecesListView.selection[0] != nil) + { + m_parent.pieces[m_piecesListView.selection[0]].masterLevel_(caller.value); + }; + + m_parent.createSaveFile(m_parent.saveFileLoc); + + m_sfView.focus; }); - m_matricesButtonLayout.add(m_matrixRemoveButton); + m_masterVolumeSlider = Slider().orientation_(\horizontal).action_({ | caller | + m_masterVolumeNumberBox.valueAction_(caller.value); + }); - m_matricesLayout.add(m_matricesButtonLayout); + if(Main.versionAtLeast(3,9)) + { + m_meterBridge = SuperDiffuse_LevelMeters(m_parent.numOuts); + } + { + "The MeterBridge requires SuperCollider >= 3.9.3".warn; - m_leftLayout.add(m_matricesLayout); + m_meterBridge = StaticText().string_("MeterBridge (requires SuperCollider version >= 3.9.3)").align_(\center); + }; - m_topLayout.add(m_leftLayout); - m_topLayout.setStretch(m_leftLayout,0); + m_controlsConfigButton = Button().states_([["Configure Control Faders"]]).action_({m_parent.configureOutFaders(); m_sfView.focus;}); + m_saveButton = Button() + .states_([["Save Concert Configuration"]]) + .mouseDownAction_({ | caller, x, y, modifiers, buttonNumber, clickCount | + if((m_parent.saveFileLoc == "") || (clickCount > 1) ) + { + Dialog.savePanel({ | path | + m_parent.createSaveFile(path); + }); + } + { + m_parent.createSaveFile(m_parent.saveFileLoc); + }; + }) + .mouseUpAction_({m_sfView.focus;}); + + m_midiConfigButton = Button().states_([["Configure MIDI"]]).action_({m_parent.configureMIDI; m_sfView.focus;}); + + m_filterConfigButton = Button().states_([["Configure Filters"]]).action_({m_parent.configureFilters; m_sfView.focus; }); + + m_clock = SuperDiffuse_Clock(); + + m_layout.addSpanning( + m_parent.controls.gui, + row: 0, + column: 2, + rowSpan: 1, + columnSpan: 1 + ); + + m_layout.addSpanning( + if(Main.versionAtLeast(3,9)) + { + m_meterBridge.view + } + { + m_meterBridge; + }, + row: 1, + column: 2, + rowSpan: 1, + columnSpan: 1 + ); + m_layout.addSpanning( + HLayout( + m_controlsConfigButton, + m_midiConfigButton, + m_filterConfigButton, + m_saveButton, + Button().states_([["Lock Interface"],["Unlock Interface"], ["Unlock Interface"]]) + .action_({ | caller | + this.lockInterface(caller.value > 0); m_sfView.focus; + }), + Button().states_([["Hide Waveform"], ["Show Waveform"]]).action_({| caller | + var tglState = (1 - caller.value).asBoolean; + + m_sfViewHidden = caller.value.asBoolean; + + m_sfView.drawsWaveForm_(tglState); + m_sfView.timeCursorOn_(tglState); + + if(m_locked == false) + { + m_sfView.acceptsMouse_(tglState); + }; + + m_sfView.focus; + + }) + ), + row: 2, + column: 2, + rowSpan: 1, + columnSpan: 1 + ); + + m_layout.addSpanning( + HLayout( + nil, + m_clock.gui, + nil, + StaticText().string_("Master:"), + m_masterVolumeSlider, + m_masterVolumeNumberBox, + ).margins_([0,15,0,15]), + row: 3, + column: 2, + rowSpan: 1, + columnSpan: 1 + ); + } + + prCreateSfView { m_sfView = SoundFileView() .minWidth_(500) .gridOn_(false) @@ -421,51 +612,19 @@ SuperDiffuse_ConcertGUI : SuperDiffuse_Observer { } }); - m_rightLayout.add(m_parent.controls.gui,1); - - m_controlsConfigButton = Button().states_([["Configure Control Faders"]]).action_({m_parent.configureOutFaders()}); - m_saveButton = Button().states_([["Save Concert Configuration"]]).action_({ - Dialog.savePanel({ | path | - m_parent.createSaveFile(path); - }); - }); - m_midiConfigButton = Button().states_([["Configure MIDI"]]).action_({m_parent.configureMIDI;}); - - m_filterConfigButton = Button().states_([["Configure Filters"]]).action_({m_parent.configureFilters;}); - - m_rightLayout.add( - HLayout( - m_controlsConfigButton, - m_midiConfigButton, - m_filterConfigButton, - m_saveButton, - Button().states_([["Lock Interface"],["Unlock Interface"]]).action_({ | caller | this.lockInterface(caller.value); }) - ).margins_([10,10,20,20]) + m_layout.addSpanning( + m_sfView, + row: 4, + column: 0, + rowSpan: 1, + columnSpan: 3 ); - - m_clock = SuperDiffuse_Clock(); - - m_rightLayout.add(HLayout(nil, m_clock.gui, nil, StaticText().string_("Master:"),m_masterVolumeSlider, m_masterVolumeNumberBox).margins_([10,10,20,20])); - - m_topLayout.add(m_rightLayout); - m_topLayout.setStretch(m_rightLayout,2); - - m_mainLayout.add(m_topLayout); - m_mainLayout.add(m_sfView, 3); - - m_win.onClose_({ this.stop; m_parent.clear; }); - - m_win.view.keyDownAction_({ | caller, modifiers, unicode, keycode | - case - {keycode == 32} { if(m_parent.isPlaying) { this.stop } { this.play }; } - {keycode == 13} { m_sfView.timeCursorPosition_(0); m_clock.reset; m_sfView.setSelection(0,[0,0]); } - }); - - m_win.front; } lockInterface { | state | - var invState = 1 - state; + var invState = state.not; + + m_locked = state.asBoolean; m_matricesListView.enabled_(invState); m_matrixAddButton.enabled_(invState); @@ -481,25 +640,17 @@ SuperDiffuse_ConcertGUI : SuperDiffuse_Observer { if(state.asBoolean) { - m_piecesListView.keyDownAction_({ | caller, modifiers, unicode, keycode | - if( (caller.selection[0] != nil) && (keycode == 32) ) - { - if(m_parent.isPlaying) - { - this.stop; - } - { - this.play(caller.selection[0]); - }; - }; - }); - m_matricesListView.keyDownAction_({}); + m_sfView.acceptsMouse_(false); } { - m_piecesListView.keyDownAction_(m_pieceEditFunc); - m_matricesListView.keyDownAction_(m_matrixEditFunc); + if(m_sfViewHidden == false) + { + m_sfView.acceptsMouse_(true); + }; }; + + m_sfView.focus; } updatePieces { @@ -526,10 +677,38 @@ SuperDiffuse_ConcertGUI : SuperDiffuse_Observer { } } - update { - this.updatePieces; - this.updateMatrices; - this.updateSFView; + updateWindowTitle { + var winTitle = "SuperDiffuse | v" ++ SuperDiffuse.version; + + if(m_parent.saveFileLoc != "") + { + winTitle = winTitle ++ " - " ++ PathName(m_parent.saveFileLoc).fileNameWithoutExtension; + }; + + m_win.name = winTitle; + } + + update { | notifyType | + switch(notifyType) + {\pieceAdded}{ + this.updatePieces; + if(m_firstPiece) + { + this.loadFirstPiece; + } + } + {\pieceRemoved}{ this.updatePieces; this.updateSFView; } + {\pieceChanged}{ this.updateSFView; } + {\matrixAdded}{ this.updateMatrices; } + {\matrixRemoved}{ this.updateMatrices; } + {\saveFileLocChanged}{ this.updateWindowTitle; } + // default: + { + this.updatePieces; + this.updateMatrices; + this.updateSFView; + this.updateWindowTitle; + } } updatePlayhead { | start, end, sampleRate | @@ -562,10 +741,11 @@ SuperDiffuse_ConcertGUI : SuperDiffuse_Observer { m_playheadRoutine.play(SystemClock); } - ready { + loadFirstPiece { if(m_piecesListView.items.size > 0) { m_piecesListView.valueAction_(0); + m_firstPiece = false; } } diff --git a/Classes/Components/SuperDiffuse_ControlFader.sc b/Classes/Components/SuperDiffuse_ControlFader.sc index fea17c5..cfc8be2 100644 --- a/Classes/Components/SuperDiffuse_ControlFader.sc +++ b/Classes/Components/SuperDiffuse_ControlFader.sc @@ -43,13 +43,13 @@ SuperDiffuse_ControlFader : SuperDiffuse_Subject { // Values should be between 0-1 // They are scaled here to have an exponential response value_ { | v | - m_value = v.pow(2); - AppClock.sched(0,{m_slider.value_(v)}); + m_value = v ** 2; + this.action; } valueAction_ { | v | this.value_(v); - this.action; + AppClock.sched(0,{m_slider.value_(v)}); } assignMIDI { | midiChan, midiCC | @@ -92,7 +92,9 @@ SuperDiffuse_ControlFader : SuperDiffuse_Subject { m_layout = VLayout(); // Slider displays linearly, but values are actually exponential - this stops graphical 'slipping' when moving manually - m_slider = Slider().maxWidth_(50).action_({|v| this.valueAction_(v.value)}).value_(this.value); + m_slider = Slider().minWidth_(10).maxWidth_(50).maxHeight_(100).action_({|v| + this.value_(v.value); + }).value_(this.value); //m_midiLearnButton = Button().maxWidth_(50).states_([["L"]]).action_({ this.learn; }); m_layout.add(m_slider,align:\center); //m_layout.add(m_midiLearnButton); diff --git a/Classes/Components/SuperDiffuse_LevelMeters.sc b/Classes/Components/SuperDiffuse_LevelMeters.sc new file mode 100644 index 0000000..33425c3 --- /dev/null +++ b/Classes/Components/SuperDiffuse_LevelMeters.sc @@ -0,0 +1,69 @@ +SuperDiffuse_LevelMeters +{ + classvar cl_responder; + + var m_numOuts; + var m_levelMeters; + + *new { | numOuts | + ^super.new.init(numOuts); + } + + *initClass { + cl_responder = nil; // lazy init + } + + init { | numOuts | + m_numOuts = numOuts; + + m_levelMeters = numOuts.collect({ + LevelIndicator() + .warning_(0.6) + .critical_(0.95) + .drawsPeak_(true) + .style_(\led) + .maxHeight_(100) + .numSteps_(32) + }); + + if(cl_responder.isNil) + { + cl_responder = OSCFunc({ | msg | + m_levelMeters.do({ | meter, index | + var peakVal = msg[2 * index + 3]; + var rmsVal = msg[2 * index + 4]; + + AppClock.sched(0, { + meter.peakLevel_(peakVal.ampdb.linlin(-80, 0, 0, 1, \min)); + meter.value_(rmsVal.ampdb.linlin(-80, 0, 0, 1)); + }); + }); + }, '/SuperDiffuse/OutLevels'); + }; + } + + view { + var view = View(); + var layout = HLayout(); + + view.onResize_({ + m_levelMeters.do(_.numSteps_(32)); + }); + + layout.add(); + + m_levelMeters.do({ | meter | + layout.add(meter); + layout.add(); + }); + + + view.layout_(layout); + ^view; + } + + free { + cl_responder.free; + cl_responder = nil; + } +} \ No newline at end of file diff --git a/Classes/SuperDiffuse_Concert.sc b/Classes/SuperDiffuse_Concert.sc index 82531cf..7159d1f 100644 --- a/Classes/SuperDiffuse_Concert.sc +++ b/Classes/SuperDiffuse_Concert.sc @@ -1,23 +1,35 @@ /* Convenience wrapper */ SuperDiffuse { + classvar Server.default.options.numOutputBusChannels) { Error("Server doesn't have enough output channels - update Server.default.options.numOutputBusChannels").throw; - } - { - ^SuperDiffuse_Concert(numIns, numOuts, numControls); - } + }; + + concert = SuperDiffuse_Concert(numIns, numOuts, numControls); + gui = SuperDiffuse_ConcertGUI(concert); + + ^concert; } *load { | pathToSaveFile | - var dic, concert; + var dic, concert, gui; File.use(pathToSaveFile, "r", { | file | dic = interpret(file.readAllString); }); concert = SuperDiffuse_Concert(dic[\setupInfo][0], dic[\setupInfo][1], dic[\setupInfo][2]); + concert.setSaveFileLoc(pathToSaveFile); dic[\pieces].do({|pieceInfo| var piece = SuperDiffuse_Piece(pieceInfo[0]); @@ -96,7 +108,7 @@ SuperDiffuse { concert.assignMIDI(ind, conf[0], conf[1]); }); - concert.loaded; + gui = SuperDiffuse_ConcertGUI(concert); ^concert; } @@ -120,11 +132,12 @@ SuperDiffuse_Concert : SuperDiffuse_Subject { var m_inBus, m_outBus, m_controlBus; var m_patchers; var m_inGroup, m_inFxGroup, m_patcherGroup, m_outFxGroup, m_outGroup; - var m_concertGUI; var m_filterManager; var m_playingPiece; + var m_saveFileLoc = ""; + *new { | numIns, numOuts, numControls | ^super.new.ninit(numIns,numOuts,numControls); } @@ -154,13 +167,14 @@ SuperDiffuse_Concert : SuperDiffuse_Subject { m_numOuts.do({ | i | m_outFaders.add(SuperDiffuse_OutFader(m_masterControl.fader(i%numControls), m_controlBus.subBus(i))); }); - m_concertGUI = SuperDiffuse_ConcertGUI(this); - ("\n\n*** Welcome to SuperDiffuse ***\nCopyright(c) James Surgenor, 2016\nDeveloped at the University of Sheffield Sound Studios\n\n").postln; + Post << "*** Welcome to SuperDiffuse ***" "\nVersion " << SuperDiffuse.version << "\nCopyright(c) James Surgenor, 2016-2019\nDeveloped at the University of Sheffield Sound Studios\n\n"; Synth(\sd_outsynth,[\in, m_outBus, \control, m_controlBus], m_outGroup); + } - m_concertGUI.update; + notify { | notifyType | + m_observers.do(_.update(notifyType)); } registerSynthDefs { @@ -177,7 +191,11 @@ SuperDiffuse_Concert : SuperDiffuse_Subject { sig = In.ar(in,m_numOuts); amps = In.kr(control, m_numOuts); - Out.ar(0, sig * Lag.kr(amps) * masterLevel); + sig = sig * Lag.kr(amps) * masterLevel; + + SendPeakRMS.kr(sig, 10, 3, '/SuperDiffuse/OutLevels'); + + Out.ar(0, sig); }).add; "Adding sd_filterSynth".inform; @@ -222,23 +240,33 @@ SuperDiffuse_Concert : SuperDiffuse_Subject { if(piece.isKindOf(SuperDiffuse_Piece) && (m_pieces.includesEqual(piece) != true)) { m_pieces.add(piece); - this.notify; + this.notify(\pieceAdded); } } - addMatrix { | name | - m_matrices.add(SuperDiffuse_Matrix.newFrom(m_matrixMaster, name)); - m_observers.do(_.updateMatrices); + addMatrix { | name, refIndex=nil | + var refMatrix = m_matrixMaster; + + if(refIndex != nil) + { + refMatrix = m_matrices[refIndex]; + name = refMatrix.name + "Copy"; + }; + + m_matrices.add(SuperDiffuse_Matrix.newFrom(refMatrix, name)); + + this.notify(\matrixAdded); } removePiece { | piece | m_pieces.remove(piece); - this.notify; + this.notify(\pieceRemoved); } removeMatrix { | matrix | m_matrices.remove(matrix); m_observers.do(_.updateMatrices); + this.notify(\matrixRemoved); } loadMatrix { | matrix | @@ -352,6 +380,7 @@ SuperDiffuse_Concert : SuperDiffuse_Subject { }) ) )); + win.onClose_({this.createSaveFile(m_saveFileLoc);}); win.front; } @@ -396,6 +425,7 @@ SuperDiffuse_Concert : SuperDiffuse_Subject { ) ) ); + win.onClose_({this.createSaveFile(m_saveFileLoc);}); win.front; } @@ -412,9 +442,21 @@ SuperDiffuse_Concert : SuperDiffuse_Subject { filterList = ListView().items_(m_filterManager.names).keyDownAction_({ | caller, char, modifiers, unicode, keycode, key | if(caller.hasFocus) { - if( (caller.selection[0] != nil) && (key == 0x45) && (modifiers.isCtrl || modifiers.isCmd)) + var selectedFilterSet = caller.selection[0]; + + if(selectedFilterSet != nil) { - m_filterManager[caller.selection[0]].gui({caller.items_(m_filterManager.names); m_filterManager.reload; }); + case + { (key == 0x45) && (modifiers.isCtrl || modifiers.isCmd) } { + m_filterManager[caller.selection[0]].gui({caller.items_(m_filterManager.names); m_filterManager.reload; }); + } + { (key == 0x44) && (modifiers.isCtrl || modifiers.isCmd ) } + { + var filterSet = m_filterManager[selectedFilterSet]; + + m_filterManager.addFilterSet(SuperDiffuse_FilterSet.newFrom(filterSet, filterSet.name + "Copy")); + filterList.items_(m_filterManager.names); + }; } } }); @@ -424,9 +466,15 @@ SuperDiffuse_Concert : SuperDiffuse_Subject { filterList.items_(m_filterManager.names); }); - layout.add(VLayout(filterList, HLayout(filterAddButton))); + filterRemoveButton = Button().states_([["-"]]).action_({ + m_filterManager.removeFilterSet(filterList.selection[0]); + filterList.items_(m_filterManager.names); + }); + + layout.add(VLayout(filterList, HLayout(filterAddButton, filterRemoveButton))); win.layout_(layout); + win.onClose_({this.createSaveFile(m_saveFileLoc);}); win.front; } @@ -481,31 +529,43 @@ SuperDiffuse_Concert : SuperDiffuse_Subject { createSaveFile { | path | var dic; - dic = Dictionary(); + if(path != "") + { + this.setSaveFileLoc(path); + + dic = Dictionary(); - dic.add(\setupInfo -> [m_numIns, m_numOuts, m_numControls]); - dic.add(\pieces -> m_pieces.collect({|piece| [piece.path, piece.name, piece.matrixInd, piece.masterLevel, piece.filterInd] })); - dic.add(\matrices -> m_matrices.collect({|matrix| [matrix.name, matrix.matrix] })); + dic.add(\setupInfo -> [m_numIns, m_numOuts, m_numControls]); + dic.add(\pieces -> m_pieces.collect({|piece| [piece.path, piece.name, piece.matrixInd, piece.masterLevel, piece.filterInd] })); + dic.add(\matrices -> m_matrices.collect({|matrix| [matrix.name, matrix.matrix] })); - dic.add( - \filterSets -> - m_filterManager.filterSets.collect({| filterSet | - [filterSet.name, filterSet.inFilters.collect({|fu| fu.saveInfo; }), filterSet.outFilters.collect({|fu| fu.saveInfo; })]; - }) - ); + dic.add( + \filterSets -> + m_filterManager.filterSets.collect({| filterSet | + [filterSet.name, filterSet.inFilters.collect({|fu| fu.saveInfo; }), filterSet.outFilters.collect({|fu| fu.saveInfo; })]; + }) + ); - dic.add(\controlsConfig -> m_outFaders.collect({|outFader| m_masterControl.indexOf(outFader.subject)})); - dic.add(\midiConfig -> m_masterControl.faders.collect({|fader| [fader.midiChan, fader.midiCC]})); + dic.add(\controlsConfig -> m_outFaders.collect({|outFader| m_masterControl.indexOf(outFader.subject)})); + dic.add(\midiConfig -> m_masterControl.faders.collect({|fader| [fader.midiChan, fader.midiCC]})); - File.use(path, "w", { | file | - file.write(dic.asCompileString); - }); + File.use(path, "w", { | file | + file.write(dic.asCompileString); + }); + } + } + + setSaveFileLoc { | path | + if(path != m_saveFileLoc) + { + m_saveFileLoc = path; + this.notify(\saveFileLocChanged); + }; } - loaded { - // should only be called from SuperDiffuse.load function - indicates we have loaded from a file and the first piece needs loading up.. - m_concertGUI.ready; + saveFileLoc { + ^ m_saveFileLoc; } play { | index, start, end | @@ -531,4 +591,16 @@ SuperDiffuse_Concert : SuperDiffuse_Subject { m_outGroup.set(\masterLevel, level); } + numIns { + ^m_numIns; + } + + numOuts { + ^m_numOuts; + } + + numControls { + ^m_numControls; + } + } \ No newline at end of file diff --git a/Classes/SuperDiffuse_Piece.sc b/Classes/SuperDiffuse_Piece.sc index ed78563..9c94471 100644 --- a/Classes/SuperDiffuse_Piece.sc +++ b/Classes/SuperDiffuse_Piece.sc @@ -65,13 +65,11 @@ SuperDiffuse_Piece { } == { | b | - if(b.isKindOf(SuperDiffuse_Piece)) - { - ^(m_path == b.path); - } - { - ^false; - }; + ^this.compareObject(b, #[\m_path]); + } + + hash { + ^this.instVarHash(#[\m_path]); } matrixInd { diff --git a/HelpSource/Tutorials/01-IntroductionToSuperDiffuse.schelp b/HelpSource/Tutorials/01-IntroductionToSuperDiffuse.schelp index 5f61a95..96e359d 100644 --- a/HelpSource/Tutorials/01-IntroductionToSuperDiffuse.schelp +++ b/HelpSource/Tutorials/01-IntroductionToSuperDiffuse.schelp @@ -32,6 +32,8 @@ section::The single-window GUI SuperDiffuse uses a single-window interface: image::superdiffuseoverview.png:: +note::Since v1.4.0, the GUI now has a meter bridge to display the output levels (post-master fader):: + The three main sections are outlined below, and have their own tutorial pages: subsection::Pieces diff --git a/HelpSource/Tutorials/03-MatricesInSuperDiffuse.schelp b/HelpSource/Tutorials/03-MatricesInSuperDiffuse.schelp index 140deb5..e731542 100644 --- a/HelpSource/Tutorials/03-MatricesInSuperDiffuse.schelp +++ b/HelpSource/Tutorials/03-MatricesInSuperDiffuse.schelp @@ -38,3 +38,5 @@ note:: You cannot remove the default matrix :: +section::Duplicating matrices +To create a copy of a matrix, select it in the list and press strong::Ctrl + d::. \ No newline at end of file diff --git a/HelpSource/Tutorials/04-ControlsInSuperDiffuse.schelp b/HelpSource/Tutorials/04-ControlsInSuperDiffuse.schelp index 2b49883..e02a00d 100644 --- a/HelpSource/Tutorials/04-ControlsInSuperDiffuse.schelp +++ b/HelpSource/Tutorials/04-ControlsInSuperDiffuse.schelp @@ -12,7 +12,9 @@ section::The Master Fader Below the configuration buttons is a Master fader. This is a global, soft-master to set the overall volume of the system. Although this is a global control, strong::each piece remembers the value they last used and automatically resets the value of this fader when reloaded.:: note:: -Although this value is remembered between switching pieces, it will not be retained if the system is restarted unless the configuration is saved once the value is set. strong::Save the configuration after setting the master to remember it:: +Although this value is remembered between switching pieces, it will not be retained if the system is restarted unless the configuration has been saved at least once. strong::Save the concert configuration at least once to automatically save changes::. + +After saving for the first time, SuperDiffuse will automatically update the save file with any changes. :: section::Mapping control faders to output faders @@ -57,6 +59,8 @@ This is seemingly the most important part of SuperDiffuse - playback. As is stan warning:: The Spacebar will only start/stop playback if the last interface object interacted with was the Pieces List, or the SoundFileView + +Right-clicking on the SoundFileView is the quickest way to reset the focus, without changing anything. :: The reasons for this are that the expected use is during a concert, or a rehearsal. diff --git a/HelpSource/Tutorials/05-FiltersInSuperDiffuse.schelp b/HelpSource/Tutorials/05-FiltersInSuperDiffuse.schelp index 17678d6..2f1ec4d 100644 --- a/HelpSource/Tutorials/05-FiltersInSuperDiffuse.schelp +++ b/HelpSource/Tutorials/05-FiltersInSuperDiffuse.schelp @@ -15,7 +15,13 @@ Pressing the "Configure Filters" button will open the configuration window: image::filtersets-config-dialog.png:: -Pressing the "+" button will add a new filter set. +Pressing the strong::[+]:: button will add a new filter set. + +Pressing the strong::[-]:: button will remove the selected filter set +note::You cannot remove the Default matrix:: + +section::Duplicating Filter Sets +To duplicate a filter set, select it in the list and press strong::Ctrl + d::. subsection:: Editing Filter Sets diff --git a/HelpSource/Tutorials/06-LoadingSavingConcerts.schelp b/HelpSource/Tutorials/06-LoadingSavingConcerts.schelp index f73d62f..884dc1a 100644 --- a/HelpSource/Tutorials/06-LoadingSavingConcerts.schelp +++ b/HelpSource/Tutorials/06-LoadingSavingConcerts.schelp @@ -14,6 +14,7 @@ list:: ## The details of each piece (order in concert is also retained) - file location, name, matrix association, master fader value ## The details of each matrix - name, matrix values ## Control Fader assignments - Out1 listens to control1 etc... +## The details of each filter set - name, settings etc... ## MIDI Configuration - MIDI Chan and CC for each Control Fader :: @@ -24,6 +25,12 @@ note:: Although it does not matter, it is recommended to use the extension ".sdc" (SuperDiffuse Configuration). :: +note:: +SuperDiffuse will remember this savefile location and save any necessary changes there automatically. + +To save into a new file, a Save As, double-click the "Save Concert Configuration" button. +:: + section::Loading Concerts If we are loading an existing concert configuration, we do not instantiate SuperDiffuse in the normal way, we instead call: diff --git a/HelpSource/Tutorials/07-SummaryForConcerts.schelp b/HelpSource/Tutorials/07-SummaryForConcerts.schelp index f1379fc..9253e6a 100644 --- a/HelpSource/Tutorials/07-SummaryForConcerts.schelp +++ b/HelpSource/Tutorials/07-SummaryForConcerts.schelp @@ -10,7 +10,7 @@ section::Starting fresh See: link::Tutorials/01-IntroductionToSuperDiffuse##Starting a new concert:: section::Loading a Concert -See: link::Tutorials/05-LoadingSavingConcerts#Loading Concerts#Loading concerts:: +See: link::Tutorials/06-LoadingSavingConcerts#Loading Concerts#Loading concerts:: section::Playing pieces See: link::Tutorials/04-ControlsInSuperDiffuse#Playing back pieces#Playing back pieces:: @@ -20,10 +20,14 @@ SuperDiffuse will stop playing automatically when it reaches the end - do not pr section::During rehearsals warning:: -Remember that the master volume will only be saved to the configuration file if you save the configuration file after it's set! - -It will remember when switching between pieces, but not reloading from a save file. +The master level will automatically save (since v1.4.0) into the save file strong::provided the concert has been saved at least once::. :: section::Locking the interface To avoid accidentally changing critical settings during concerts, the "Lock Interface" button will disable any editing functionality of the GUI. +note:: To unlock the interface again, click Unlock button twice.:: + +Since v1.4.0, locking the interface will now disable mouse interaction with the SoundFileView. This provides a safer 'Concert Mode' approach. + +section::Hiding the waveform view +Since v1.4.0, you can hide the waveform view. In addition to hiding the wavefrom, it will disable mouse interaction with the SoundFileView (keyboard shortcuts will still work). diff --git a/HelpSource/Tutorials/filtersets-config-dialog.png b/HelpSource/Tutorials/filtersets-config-dialog.png index b5f7562..23c9eda 100644 Binary files a/HelpSource/Tutorials/filtersets-config-dialog.png and b/HelpSource/Tutorials/filtersets-config-dialog.png differ diff --git a/HelpSource/Tutorials/superdiffuseoverview.png b/HelpSource/Tutorials/superdiffuseoverview.png index d56faff..308018a 100644 Binary files a/HelpSource/Tutorials/superdiffuseoverview.png and b/HelpSource/Tutorials/superdiffuseoverview.png differ diff --git a/SuperDiffuse.quark b/SuperDiffuse.quark index 7505ab7..b79a9b0 100644 --- a/SuperDiffuse.quark +++ b/SuperDiffuse.quark @@ -2,7 +2,7 @@ \name: "SuperDiffuse", \summary: "An n-Channel diffusion system", \author: "James Surgenor", - \version: "1.3.0", + \version: "1.4.0", \organization: "University of Sheffield Sound Studios", \since: "2016", \url: "https://github.com/jrsurge/SuperDiffuse"