1 module soundtab.ui.sndcanvas;
2 
3 import dlangui.widgets.widget;
4 import soundtab.ui.noteutil;
5 import soundtab.ui.synthwidget;
6 import std..string : format;
7 import std.math : exp, log, exp2, log2, floor;
8 import dlangui.core.events;
9 
10 class SoundCanvas : Widget {
11 
12     uint _darkNoteColor = 0x403020;
13     uint _whiteNoteColor = 0x605040;
14     uint _spaceColor = 0x201008;
15     uint _noteNameColor = 0x808080;
16     uint _pitchMarkColor = 0xFF0000;
17 
18     double _minPitch = 440.0 / 8;
19     double _maxPitch = 440.0 * 8;
20     double _minNote;
21     double _maxNote;
22 
23     double _currentPitch = 478;
24     double _currentY = 0.5;
25 
26     @property double minPitch() { return _minPitch; }
27     @property double maxPitch() { return _maxPitch; }
28     @property double minNote() { return _minNote; }
29     @property double maxNote() { return _maxNote; }
30 
31     void setPitchRange(double minfreq, double maxfreq) {
32         _minPitch = minfreq;
33         _maxPitch = maxfreq;
34         _minNote = toLogScale(_minPitch);
35         _maxNote = toLogScale(_maxPitch);
36     }
37 
38     void setNoteRange(double minnote, double maxnote) {
39         _minNote = minnote;
40         _maxNote = maxnote;
41         _minPitch = fromLogScale(_minNote);
42         _maxPitch = fromLogScale(_maxNote);
43     }
44 
45     void setNoteRange(int minnote, int maxnote) {
46         _minNote = minnote - 0.49;
47         _maxNote = maxnote + 0.49;
48         //Log.d("SoundCanvas.setNoteRange: ", _minNote, " .. ", _maxNote, " (", noteToFullName(_minNote), " .. ", noteToFullName(_maxNote), ")");
49         _minPitch = fromLogScale(_minNote);
50         _maxPitch = fromLogScale(_maxNote);
51     }
52 
53     @property double pitch() { return _currentPitch; }
54     @property double controller1() { return _currentY; }
55     void setPosition(double x, double y, double pressure) {
56         double note = _minNote + (_maxNote - _minNote) * x;
57         _currentPitch = fromLogScale(note);
58         _currentY = y;
59         invalidate();
60     }
61 
62 
63     SynthWidget _synth;
64     this(SynthWidget synth) {
65         super("soundCanvas");
66         _synth = synth;
67         layoutWidth = FILL_PARENT;
68         layoutHeight = FILL_PARENT;
69         backgroundColor = 0xFFFFFF;
70         ///*
71         double _halfTone = 1.05946309435929530980;
72         _halfTone = exp2(log2(2.0) / 12);
73         Log.d("halfTone = ", "%1.20f".format(_halfTone));
74         double _quarterTone = 1.05946309435929530980;
75         _quarterTone = exp2(log2(2.0) / 24);
76         Log.d("quarterTone = ", "%1.20f".format(_quarterTone));
77         double testOctave = _halfTone * _halfTone * _halfTone * _halfTone * _halfTone * 
78             _halfTone * _halfTone * _halfTone * _halfTone * _halfTone * _halfTone * _halfTone;
79         Log.d("octave = ", "%1.20f".format(testOctave));
80 
81         Log.d("toLogScale(440)=", toLogScale(440));
82         Log.d("toLogScale(220)=", toLogScale(220));
83         Log.d("toLogScale(880)=", toLogScale(880));
84         Log.d("toLogScale(440 + half tone)=", toLogScale(440 * HALF_TONE));
85         Log.d("fromLogScale(12)=", fromLogScale(12));
86         
87         Log.d("noteNameToNote(A4)=", fullNameToNote("A4"));
88         Log.d("noteNameToNote(C5)=", fullNameToNote("C5"));
89 
90         import std.math : round;
91         for (int i = -25; i < 25; i++) {
92             Log.d("note=", i, " name=", noteToFullName(i), " fullNameToNote=", fullNameToNote(noteToFullName(i)), " freq=", fromLogScale(i), " noteIndex=", round(toLogScale(fromLogScale(i))), " isBlack=", isBlackNote(i));
93         }
94 
95         //*/
96         setNoteRange(-36, 36);
97         trackHover = true;
98         clickable = true;
99 
100     }
101 
102     /** 
103        Measure widget according to desired width and height constraints. (Step 1 of two phase layout). 
104     */
105     override void measure(int parentWidth, int parentHeight) { 
106         measuredContent(parentWidth, parentHeight, parentWidth, parentHeight);
107     }
108 
109     int getPitchX(Rect clientRect, double note) {
110         int w = clientRect.width;
111         return clientRect.left + cast(int)((note - _minNote) / (_maxNote - _minNote) * w);
112     }
113 
114     Rect getNoteRect(Rect clientRect, double note) {
115         double nn = floor(note + 0.5);
116         double nmin = nn - 0.5;
117         double nmax = nn + 0.5;
118         int xmin = getPitchX(clientRect, nmin);
119         int xmax = getPitchX(clientRect, nmax);
120         return Rect(xmin, clientRect.top, xmax, clientRect.bottom);
121     }
122 
123     /// Draw widget at its position to buffer
124     override void onDraw(DrawBuf buf) {
125         if (visibility != Visibility.Visible)
126             return;
127         Rect rc = _pos;
128         applyMargins(rc);
129         auto saver = ClipRectSaver(buf, rc, alpha);
130         DrawableRef bg = backgroundDrawable;
131         if (!bg.isNull) {
132             bg.drawTo(buf, rc, state);
133         }
134         applyPadding(rc);
135         _needDraw = false;
136         int pixelsPerNote = cast(int)(rc.width / (_maxNote - _minNote));
137         int fsize = pixelsPerNote;
138         if (fsize < 8)
139             fsize = 8;
140         if (fsize > 32)
141             fsize = 32;
142         FontRef myfont = font;
143         FontRef fnt = FontManager.instance.getFont(fsize, 400, false, myfont.family, myfont.face);
144 
145         //================================
146         for (double n = _minNote; n <= _maxNote; n += 1) {
147             Rect noteRect = getNoteRect(rc, n);
148             //noteRect.left ++;
149             bool black = isBlackNote(n);
150             uint cl = black ? _darkNoteColor : _whiteNoteColor;
151             buf.fillRect(noteRect, cl);
152             if (!black) {
153                 dstring noteName = getNoteName(n);
154                 Point sz = fnt.textSize(noteName);
155                 fnt.drawText(buf, noteRect.middlex - sz.x / 2, noteRect.top + fsize/2, noteName, _noteNameColor);
156                 dstring octaveName = getNoteOctaveName(n);
157                 sz = fnt.textSize(octaveName);
158                 fnt.drawText(buf, noteRect.middlex - sz.x / 2, noteRect.top + fsize/2 + fsize, octaveName, _noteNameColor);
159             }
160             noteRect.right = noteRect.left + 1;
161             buf.fillRect(noteRect, _spaceColor); //black ? _darkNoteColor : _whiteNoteColor);
162         }
163         double currentNote = toLogScale(_currentPitch);
164         if (currentNote >= _minNote && currentNote <= _maxNote) {
165             int x = getPitchX(rc, currentNote);
166             int y = cast(int)(rc.top + (rc.height * _currentY));
167             buf.fillRect(Rect(x, rc.top, x+1, rc.bottom), _pitchMarkColor);
168             buf.fillRect(Rect(x - pixelsPerNote / 2, y, x + pixelsPerNote / 2, y + 1), _pitchMarkColor);
169         }
170     }
171 
172     bool _lastProximity = false;
173     override bool onMouseEvent(MouseEvent event) {
174         if (_synth.tabletInitialized)
175             return false;
176         if (event.action == MouseAction.ButtonDown || event.action == MouseAction.ButtonUp || event.action == MouseAction.Move) {
177             double x = (event.x - _pos.left) / cast(double)_pos.width;
178             double y = (event.y - _pos.top) / cast(double)_pos.height;
179             double pressure = (event.buttonFlags & MouseFlag.LButton) ? 0.5 : 0;
180             uint buttons = 0;
181             if (event.buttonFlags & MouseFlag.LButton)
182                 buttons |= 1;
183             if (event.buttonFlags & MouseFlag.RButton)
184                 buttons |= 2;
185             if (!_lastProximity) {
186                 _synth.onProximity(true);
187                 _lastProximity = false;
188             }
189             _synth.onPositionChange(x, y, pressure, buttons);
190         } else if (event.action == MouseAction.Cancel || event.action == MouseAction.Leave || event.action == MouseAction.FocusOut) {
191             if (_lastProximity) {
192                 _synth.onProximity(false);
193                 _lastProximity = false;
194             }
195         }
196         return super.onMouseEvent(event);
197     }
198     
199 }