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 }