1 /**
2  * ae.demo.inputtiming.main
3  *
4  * License:
5  *   This Source Code Form is subject to the terms of
6  *   the Mozilla Public License, v. 2.0. If a copy of
7  *   the MPL was not distributed with this file, You
8  *   can obtain one at http://mozilla.org/MPL/2.0/.
9  *
10  * Authors:
11  *   Vladimir Panteleev <ae@cy.md>
12  */
13 
14 module ae.demo.inputtiming.main;
15 
16 import core.time;
17 
18 import std.conv;
19 import std.format;
20 import std.math;
21 
22 import ae.ui.app.application;
23 import ae.ui.app.main;
24 import ae.ui.audio.mixer.software;
25 import ae.ui.audio.sdl2.audio;
26 import ae.ui.audio.source.base;
27 import ae.ui.audio.source.memory;
28 import ae.ui.shell.shell;
29 import ae.ui.shell.sdl2.shell;
30 import ae.ui.video.bmfont;
31 import ae.ui.video.renderer;
32 import ae.ui.video.sdl2.video;
33 import ae.utils.fps;
34 import ae.utils.graphics.fonts.draw;
35 import ae.utils.graphics.fonts.font8x8;
36 import ae.utils.math;
37 import ae.utils.graphics.image;
38 import ae.utils.meta;
39 
40 final class MyApplication : Application
41 {
42 	override string getName() { return "Demo/Input"; }
43 	override string getCompanyName() { return "CyberShadow"; }
44 
45 	Shell shell;
46 
47 	enum BAND_WIDTH = 800;
48 	enum BAND_INTERVAL = 100;
49 	enum BAND_TOP = 50;
50 	enum BAND_HEIGHT = 30;
51 	enum BAND_HNSECS_PER_PIXEL = 10_000_0;
52 	enum HISTORY_TOP = 150;
53 	enum HISTORY_HEIGHT = 50;
54 	enum HISTORY_LEFT = 150;
55 
56 	enum Mode { video, audio }
57 	enum Device : int { keyboard, joypad, mouse }
58 	enum SampleType : int { precision, duration }
59 
60 	int[][enumLength!SampleType][enumLength!Device] history;
61 	Mode mode;
62 	enum SAMPLE_COLORS = [BGRX(0, 0, 255), BGRX(0, 255, 0)];
63 
64 	/// Some (precise) time value of the moment, in hnsecs.
65 	@property long now() { return TickDuration.currSystemTick.to!("hnsecs", long); }
66 
67 	FontTextureSource!Font8x8 font;
68 	MemorySoundSource!SoundSample tick;
69 
70 	this()
71 	{
72 		font = new FontTextureSource!Font8x8(font8x8, BGRX(128, 128, 128));
73 		tick = memorySoundSource([short.max, short.min], 44100);
74 	}
75 
76 	override bool needSound() { return true; }
77 
78 	static long lastTick;
79 
80 	override void render(Renderer s)
81 	{
82 		s.clear();
83 		auto t = now;
84 
85 		if (!lastTick)
86 		{
87 			shell.setCaption("Press m to switch mode, Esc to exit, any other key to measure latency");
88 			lastTick = t;
89 		}
90 
91 		final switch (mode)
92 		{
93 			case Mode.video:
94 			{
95 				auto x = t / BAND_HNSECS_PER_PIXEL;
96 				s.line(BAND_WIDTH/2, BAND_TOP, BAND_WIDTH/2, BAND_TOP + BAND_HEIGHT, BGRX(0, 0, 255));
97 				foreach (lx; 0..BAND_WIDTH)
98 					if ((lx+x)%BAND_INTERVAL == 0)
99 						s.line(lx, BAND_TOP+BAND_HEIGHT, lx, BAND_TOP+BAND_HEIGHT*2, BGRX(0, 255, 0));
100 				break;
101 			}
102 			case Mode.audio:
103 			{
104 				auto str = "Press a key when you hear the tick sound";
105 				font.drawText(s, (BAND_WIDTH - str.length.to!int * font8x8.maxWidth)/2, BAND_TOP+BAND_HEIGHT, str);
106 				enum sampleInterval = BAND_HNSECS_PER_PIXEL * BAND_INTERVAL;
107 				if (t / sampleInterval != lastTick / sampleInterval)
108 					shell.audio.mixer.playSound(tick);
109 				break;
110 			}
111 		}
112 
113 		foreach (device, deviceSamples; history)
114 			foreach (sampleType, samples; deviceSamples)
115 			{
116 				auto y = HISTORY_TOP + HISTORY_HEIGHT * (device*3 + sampleType*2 + 1);
117 				s.line(0, y - HISTORY_HEIGHT, s.width, y - HISTORY_HEIGHT, BGRX.monochrome(0x40));
118 				foreach (index, sample; samples)
119 				{
120 					if (sample > HISTORY_HEIGHT)
121 						sample = HISTORY_HEIGHT;
122 					s.line(HISTORY_LEFT + index, y - sample, HISTORY_LEFT + index, y, SAMPLE_COLORS[sampleType]);
123 				}
124 			}
125 
126 		foreach (Device device, deviceSamples; history)
127 			foreach (SampleType sampleType, samples; deviceSamples)
128 			{
129 				auto y = HISTORY_TOP + HISTORY_HEIGHT * (device*3 + sampleType*2 + 1);
130 				if (sampleType == SampleType.duration) y -= HISTORY_HEIGHT/2;
131 				y -= font8x8.height / 2;
132 				font.drawText(s, 2, y.to!int, "%8s %-9s".format(device, sampleType));
133 			}
134 
135 		lastTick = t;
136 	}
137 
138 	override int run(string[] args)
139 	{
140 		shell = new SDL2Shell(this);
141 		shell.video = new SDL2Video();
142 
143 		shell.audio = new SDL2Audio();
144 		shell.audio.mixer = new SoftwareMixer();
145 
146 		shell.run();
147 		shell.video.shutdown();
148 		return 0;
149 	}
150 
151 	long pressed;
152 
153 	void keyDown(Device device)
154 	{
155 		// Skip keyDown events without corresponding keyUp
156 		if (history[device][SampleType.precision].length > history[device][SampleType.duration].length)
157 			return;
158 
159 		pressed = now;
160 		auto x = cast(int)(pressed / BAND_HNSECS_PER_PIXEL + BAND_WIDTH/2) % BAND_INTERVAL;
161 		if (x > BAND_INTERVAL/2)
162 			x -= BAND_INTERVAL;
163 		history[device][SampleType.precision] ~= x;
164 	}
165 
166 	void keyUp(Device device)
167 	{
168 		auto duration = now - pressed;
169 		history[device][SampleType.duration] ~= cast(int)(duration / BAND_HNSECS_PER_PIXEL);
170 	}
171 
172 	bool ignoreKeyUp;
173 
174 	override void handleKeyDown(Key key, dchar character)
175 	{
176 		ignoreKeyUp = true;
177 		if (key == Key.esc)
178 			shell.quit();
179 		else
180 		if (character == 'm')
181 			mode++, mode %= enumLength!Mode;
182 		else
183 		if (character == 's')
184 			shell.audio.mixer.playSound(tick);
185 		else
186 		{
187 			keyDown(Device.keyboard);
188 			ignoreKeyUp = false;
189 		}
190 	}
191 
192 	override void handleKeyUp(Key key)
193 	{
194 		if (ignoreKeyUp)
195 			ignoreKeyUp = false;
196 		else
197 			keyUp(Device.keyboard);
198 	}
199 
200 	override bool needJoystick() { return true; }
201 
202 	override void handleJoyButtonDown(int button)
203 	{
204 		keyDown(Device.joypad);
205 	}
206 
207 	override void handleJoyButtonUp  (int button)
208 	{
209 		keyUp  (Device.joypad);
210 	}
211 
212 	override void handleMouseDown(uint x, uint y, MouseButton button)
213 	{
214 		keyDown(Device.mouse);
215 	}
216 
217 	override void handleMouseUp(uint x, uint y, MouseButton button)
218 	{
219 		keyUp  (Device.mouse);
220 	}
221 
222 	override void handleQuit()
223 	{
224 		shell.quit();
225 	}
226 }
227 
228 shared static this()
229 {
230 	createApplication!MyApplication();
231 }