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 }