1 module soundtab.ui.synthwidget; 2 3 import dlangui.widgets.widget; 4 import dlangui.widgets.layouts; 5 import dlangui.widgets.controls; 6 import dlangui.widgets.combobox; 7 import dlangui.widgets.groupbox; 8 import dlangui.widgets.scrollbar; 9 import soundtab.ui.sndcanvas; 10 import soundtab.ui.actions; 11 import derelict.wintab.tablet; 12 import soundtab.ui.noteutil; 13 import soundtab.ui.pitchwidget; 14 import soundtab.ui.pressurewidget; 15 import soundtab.ui.noterangewidget; 16 import soundtab.ui.slidercontroller; 17 import soundtab.audio.playback; 18 import soundtab.audio.audiosource; 19 import soundtab.audio.instruments; 20 import soundtab.audio.mp3player; 21 22 class PlayerPanel : GroupBox { 23 import soundtab.ui.frame; 24 private SoundFrame _frame; 25 private Mp3Player _player; 26 private TextWidget _playFileName; 27 private TextWidget _playPositionText; 28 private SliderWidget _playSlider; 29 private SliderController _volumeControl; 30 this(SoundFrame frame) { 31 super("playerControls", "Accompaniment"d, Orientation.Horizontal); 32 _frame = frame; 33 layoutWidth = FILL_PARENT; 34 Widget openButton = new Button(ACTION_FILE_OPEN_ACCOMPANIMENT); 35 Widget playButton = new Button(ACTION_FILE_PLAY_PAUSE_ACCOMPANIMENT); 36 37 _volumeControl = new SliderController(ControllerId.AccompanimentVolume, "Volume"d, 0, 1000, _frame.settings.accompanimentVolume); 38 39 VerticalLayout sliderLayout = new VerticalLayout(); 40 HorizontalLayout textLayout = new HorizontalLayout(); 41 sliderLayout.layoutWidth = FILL_PARENT; 42 textLayout.layoutWidth = FILL_PARENT; 43 _playSlider = new SliderWidget("playPosition"); 44 _playSlider.layoutWidth = FILL_PARENT; 45 _playSlider.setRange(0, 10000); 46 _playSlider.position = 0; 47 _playSlider.scrollEvent = &onScrollEvent; 48 _playFileName = new TextWidget("playFileName", ""d); 49 _playFileName.alignment = Align.Center; 50 _playPositionText = new TextWidget("playPositionText", ""d); 51 _playPositionText.alignment = Align.Center; 52 textLayout.addChild(_playFileName); 53 textLayout.addChild(new HSpacer()); 54 textLayout.addChild(_playPositionText); 55 sliderLayout.addChild(textLayout); 56 sliderLayout.addChild(_playSlider); 57 sliderLayout.margins = Rect(5, 0, 5, 0).pointsToPixels; 58 sliderLayout.padding = Rect(5, 0, 5, 0).pointsToPixels; 59 60 addChild(_volumeControl); 61 addChild(openButton); 62 addChild(sliderLayout); 63 addChild(playButton); 64 _player = new Mp3Player(); 65 updatePlayPosition(); 66 _volumeControl.onChange = &onVolume; 67 } 68 69 protected void onVolume(SliderController source, int value) { 70 _frame.settings.accompanimentVolume = value; 71 _player.volume = value / 1000.0f; 72 } 73 74 PlayPosition _position; 75 string _filename; 76 77 static dstring secondsToString(float v) { 78 import std.math : round; 79 import std.format; 80 import std.utf : toUTF32; 81 int seconds = cast(int)round(v); 82 83 // utf conversion is to bypass dmd 2.072.0 bug 84 return ("%d:%02d".format(seconds / 60, (seconds % 60))).toUTF32; 85 } 86 87 void playPauseAccomp() { 88 _player.paused = !_player.paused; 89 } 90 91 void openAccompanimentFile(string filename) { 92 if (!_player.loadFromFile(filename)) { 93 if (window) 94 window.showMessageBox("MP3 file opening error"d, "Cannot load MP3 file "d); 95 } 96 } 97 98 void updatePlayPosition() { 99 import std.path : baseName; 100 import std.utf : toUTF32; 101 _filename = _player.filename; 102 _position = _player.position; 103 dstring fn = _filename ? _filename.baseName.toUTF32 : null; 104 if (!fn.length) 105 fn = "[no MP3 file opened]"d; 106 if (_playFileName.text != fn) 107 _playFileName.text = fn; 108 dchar[] positionText; 109 positionText ~= secondsToString(_position.currentPosition); 110 positionText ~= " / "d; 111 positionText ~= secondsToString(_position.length); 112 if (_playPositionText.text != positionText) 113 _playPositionText.text = cast(dstring)positionText; 114 int percent = _position.positionPercent; 115 if (_playSlider.position != percent) 116 _playSlider.position = percent; 117 } 118 119 protected bool onScrollEvent(AbstractSlider source, ScrollEvent event) { 120 if (event.action == ScrollAction.SliderMoved) { 121 //int percent = _position.positionPercent; 122 _player.position = _position.percentToSeconds(event.position); 123 //if (event.position != percent) 124 // updatePlayPosition(); 125 126 } 127 return true; 128 } 129 } 130 131 class SynthWidget : VerticalLayout, TabletPositionHandler, TabletProximityHandler { 132 import soundtab.ui.frame; 133 134 SoundFrame _frame; 135 SoundCanvas _soundCanvas; 136 VerticalLayout _controlsLayout; 137 Tablet _tablet; 138 PitchWidget _pitchWidget; 139 NoteRangeWidget _noteRangeWidget; 140 PressureWidget _pressureWidget; 141 AudioPlayback _playback; 142 Mixer _mixer; 143 Instrument _instrument; 144 HorizontalLayout _controllers; 145 PlayerPanel _playerPanel; 146 147 ComboBox _instrSelection; 148 ComboBox _yControllerSelection; 149 SliderController _chorus; 150 SliderController _reverb; 151 SliderController _vibrato; 152 SliderController _vibratoFreq; 153 SliderController _pitchCorrection; 154 SliderController _volumeControl; 155 156 PitchCorrector _corrector; 157 158 @property Mixer mixer() { return _mixer; } 159 160 this(SoundFrame frame, Tablet tablet, AudioPlayback playback) { 161 super("synth"); 162 _frame = frame; 163 _playback = playback; 164 _mixer = new Mixer(); 165 _playback.setSynth(_mixer); 166 _tablet = tablet; 167 _tablet.onProximity = this; 168 _tablet.onPosition = this; 169 170 171 layoutWidth = FILL_PARENT; 172 layoutHeight = FILL_PARENT; 173 //backgroundColor = 0xE0E8F0; 174 175 _controlsLayout = new VerticalLayout(); 176 _controlsLayout.layoutWidth = FILL_PARENT; 177 _controlsLayout.layoutHeight = WRAP_CONTENT; 178 addChild(_controlsLayout); 179 180 _noteRangeWidget = new NoteRangeWidget(); 181 _soundCanvas = new SoundCanvas(this); 182 183 _playerPanel = new PlayerPanel(_frame); 184 _mixer.addSource(_playerPanel._player); 185 _controlsLayout.addChild(_playerPanel); 186 187 GroupBox _controlsh = new GroupBox(null, "Instrument"d, Orientation.Vertical); 188 _controlsh.layoutWidth = FILL_PARENT; 189 _controlsh.layoutHeight = WRAP_CONTENT; 190 //_controlsh.margins = Rect(3,3,3,3); 191 _controlsLayout.addChild(_controlsh); 192 HorizontalLayout instrLine1 = new HorizontalLayout(); 193 HorizontalLayout instrLine2 = new HorizontalLayout(); 194 instrLine1.layoutWidth = FILL_PARENT; 195 instrLine2.layoutWidth = FILL_PARENT; 196 _controlsh.addChild(instrLine1); 197 _controlsh.addChild(instrLine2); 198 199 int corrValue = _frame.settings.getControllerValue(ControllerId.PitchCorrection, 0); 200 _pitchCorrection = new SliderController(ControllerId.PitchCorrection, "Pitch correction", 0, 1000, corrValue); 201 _pitchCorrection.onChange = &onController; 202 203 _volumeControl = new SliderController(ControllerId.InstrumentVolume, "Volume"d, 0, 1000, 1000); 204 _volumeControl.value = 1000; 205 _volumeControl.onChange = &onVolume; 206 207 Instrument[] instr = getInstrumentList(); 208 StringListValue[] instrList; 209 foreach(i; instr) { 210 instrList ~= StringListValue(i.id, i.name); 211 } 212 VerticalLayout gb = new VerticalLayout("instrgb"); 213 //gb.addChild(new TextWidget(null, "Instrument:"d)); 214 gb.margins = Rect(3, 0, 3, 0).pointsToPixels; 215 gb.padding = Rect(3, 0, 3, 0).pointsToPixels; 216 string instrId = _frame.settings.instrumentId; 217 _instrSelection = new ComboBox("instrument", instrList); 218 _instrSelection.minWidth = pointsToPixels(100); 219 int instrIndex = 0; 220 for (int i = 0; i < instr.length; i++) { 221 if (instr[i].id == instrId) 222 instrIndex = i; 223 } 224 _controllers = new HorizontalLayout(); 225 _instrSelection.itemClick = delegate(Widget source, int itemIndex) { 226 Instrument ins = instr[itemIndex]; 227 setInstrument(ins.id); 228 if (_frame.settings.instrumentId != ins.id) { 229 _frame.settings.instrumentId = ins.id; 230 _frame.settings.save(); 231 } 232 createControllers(); 233 return true; 234 }; 235 gb.addChild(_instrSelection); 236 237 VerticalLayout gb2 = new VerticalLayout("instrgb"); 238 gb2.addChild(new TextWidget(null, "Y axis controller:"d)); 239 _yControllerSelection = new ComboBox("instrument"); 240 _yControllerSelection.itemClick = delegate(Widget source, int itemIndex) { 241 _frame.settings.setControllerValue(ControllerId.YAxisController, itemIndex); 242 _yAxisControllerWidget = itemIndex > 0 ? cast(SliderController)_controllers.child(itemIndex - 1) : null; 243 _yAxisController = ControllerId.None; 244 if (_yAxisControllerWidget) { 245 import std.conv : to; 246 string strId = _yAxisControllerWidget.id; 247 if (strId.length) { 248 _yAxisController = strId.to!ControllerId; 249 _instrument.setYAxisController(_yAxisController); 250 } 251 } 252 return true; 253 }; 254 gb2.addChild(_yControllerSelection); 255 256 instrLine1.addChild(gb); 257 instrLine1.addChild(new HSpacer()); 258 instrLine1.addChild(_controllers); 259 260 _pitchWidget = new PitchWidget(); 261 _pressureWidget = new PressureWidget(); 262 263 instrLine2.addChild(_volumeControl); 264 instrLine2.addChild(_pitchWidget); 265 instrLine2.addChild(_pitchCorrection); 266 267 instrLine2.addChild(new HSpacer()); 268 269 instrLine2.addChild(_pressureWidget); 270 instrLine2.addChild(gb2); 271 272 addChild(_soundCanvas); 273 274 addChild(_noteRangeWidget); 275 276 _soundCanvas.setNoteRange(_noteRangeWidget.rangeStart, _noteRangeWidget.rangeEnd); 277 _noteRangeWidget.onNoteRangeChange = &onNoteRangeChange; 278 279 setInstrument(instrId); 280 281 string accompFile = _frame.settings.accompanimentFile; 282 if (accompFile) 283 _playerPanel.openAccompanimentFile(accompFile); 284 } 285 286 void setInstrument(string id) { 287 if (_instrument && _instrument.id == id) 288 return; 289 if (_instrument) { 290 _mixer.removeSource(_instrument); 291 } 292 Instrument[] instr = getInstrumentList(); 293 Instrument found = instr[0]; 294 int foundIndex = 0; 295 foreach(index, i; instr) { 296 if (id == i.id) { 297 found = i; 298 foundIndex = cast(int)index; 299 } 300 } 301 _instrSelection.selectedItemIndex = foundIndex; 302 _instrument = found; 303 304 createControllers(); 305 306 if (_instrument) 307 _mixer.addSource(_instrument); 308 } 309 310 ControllerId _yAxisController = ControllerId.None; 311 SliderController _yAxisControllerWidget; 312 313 /// create controllers for current instrument; set current values from settings 314 void createControllers() { 315 _controllers.removeAllChildren(); 316 immutable(Controller[]) controllers = _instrument.getControllers(); 317 StringListValue[] controllerList; 318 controllerList ~= StringListValue(0, "No Y axis mapping"d); 319 int yControllerIndex = _frame.settings.getControllerValue(ControllerId.YAxisController, 0); 320 int index = 0; 321 _yAxisController = ControllerId.None; 322 _yAxisControllerWidget = null; 323 foreach(controller; controllers) { 324 index++; 325 int value = controller.value; 326 value = _frame.settings.getControllerValue(controller.id, value); 327 if (value < controller.minValue) 328 value = controller.minValue; 329 else if (value > controller.maxValue) 330 value = controller.maxValue; 331 SliderController w = new SliderController(controller.id, controller.name, controller.minValue, controller.maxValue, value); 332 w.onChange = &onController; 333 _controllers.addChild(w); 334 _instrument.updateController(controller.id, value); 335 controllerList ~= StringListValue(controller.id, controller.name); 336 if (index == yControllerIndex) { 337 _yAxisController = controller.id; 338 _yAxisControllerWidget = w; 339 } 340 } 341 _yControllerSelection.items = controllerList; 342 _yControllerSelection.selectedItemIndex = yControllerIndex; 343 int corrValue = _frame.settings.getControllerValue(ControllerId.PitchCorrection, 0); 344 _corrector.amount = corrValue; 345 _pitchCorrection.value = corrValue; 346 int volume = _frame.settings.getControllerValue(ControllerId.InstrumentVolume, 1000); 347 _instrument.volume = volume / 1000.0f; 348 _volumeControl.value = volume; 349 _instrument.setYAxisController(_yAxisController); 350 int[2] range = _frame.settings.noteRange; 351 _noteRangeWidget.handleRangeChange(range[0], range[1]); 352 } 353 354 protected void onVolume(SliderController source, int value) { 355 //_player.volume = value / 1000.0f; 356 if (_instrument) 357 _instrument.volume = value / 1000.0f; 358 _frame.settings.setControllerValue(ControllerId.InstrumentVolume, value); 359 } 360 361 void onController(SliderController source, int value) { 362 switch (source.id) { 363 case "pitchCorrection": 364 _corrector.amount = value; 365 break; 366 default: 367 break; 368 } 369 _frame.settings.setControllerValue(source.controllerId, value); 370 _instrument.updateController(source.controllerId, value); 371 } 372 373 void onNoteRangeChange(int minNote, int maxNote) { 374 _soundCanvas.setNoteRange(minNote, maxNote); 375 _frame.settings.noteRange = [minNote, maxNote]; 376 } 377 378 @property bool tabletInitialized() { return _tablet.isInitialized; } 379 380 bool _proximity = false; 381 void onPositionChange(double x, double y, double pressure, uint buttons) { 382 _soundCanvas.setPosition(x, y, pressure); 383 double pitch = _corrector.correctPitch(_soundCanvas.pitch); 384 _instrument.setSynthParams(pitch, pressure, y); 385 if (_yAxisControllerWidget) 386 _yAxisControllerWidget.value = cast(int)((1 - y) * 1000); 387 _pitchWidget.setPitch(pitch); 388 _noteRangeWidget.setPitch(pitch); 389 _pressureWidget.setPressure(pressure, _proximity); 390 invalidate(); 391 window.update(); 392 } 393 394 void onProximity(bool enter) { 395 if (_proximity != enter) { 396 _proximity = enter; 397 _pressureWidget.setPressure(0, _proximity); 398 _playback.paused = !enter; 399 invalidate(); 400 window.update(); 401 } 402 } 403 404 void updatePlayPosition() { 405 _playerPanel.updatePlayPosition(); 406 } 407 408 void playPauseAccomp() { 409 _playerPanel.playPauseAccomp(); 410 } 411 412 void openAccompanimentFile(string filename) { 413 _playerPanel.openAccompanimentFile(filename); 414 } 415 }