1 module test1.app;
2 
3 import std.stdio;
4 import std.string;
5 import std.conv;
6 import std.format;
7 import std.random;
8 import std.typecons : BitFlags;
9 
10 import bindbc.sdl;
11 
12 import midi2.types.structs;
13 import midi2.types.enums;
14 
15 import pixelperfectengine.concrete.window;
16 import pixelperfectengine.concrete.windowhandler;
17 import pixelperfectengine.concrete.eventchainsystem;
18 
19 import pixelperfectengine.graphics.outputscreen;
20 import pixelperfectengine.graphics.raster;
21 import pixelperfectengine.graphics.layers;
22 
23 import pixelperfectengine.graphics.bitmap;
24 
25 import pixelperfectengine.system.input;
26 import pixelperfectengine.system.file;
27 import pixelperfectengine.system.etc;
28 import pixelperfectengine.system.systemutility;
29 import pixelperfectengine.system.config;
30 import pixelperfectengine.system.timer;
31 
32 import pixelperfectengine.system.common;
33 
34 import pixelperfectengine.audio.base.handler;
35 import pixelperfectengine.audio.base.modulebase;
36 import pixelperfectengine.audio.base.config;
37 import pixelperfectengine.audio.base.midiseq;
38 //import pixelperfectengine.audio.modules.qm816;
39 import core.thread;
40 import iota.audio.midi;
41 import iota.audio.midiin;
42 
43 import test1.audioconfig;
44 import test1.preseteditor;
45 import test1.modulerouter;
46 import test1.virtmidikeyb;
47 import test1.midiseq;
48 
49 /** 
50  * Audio subsystem test.
51  */
52 int main(string[] args) {
53 	initialzeSDL();
54 	AudioDevKit app = new AudioDevKit(args);
55 	app.whereTheMagicHappens();
56 	return 0;
57 }
58 ///Top level window, so far only containing the MenuBar.
59 public class TopLevelWindow : Window {
60 	MenuBar mb;
61 	AudioDevKit app;
62 	public this(int width, int height, AudioDevKit app) {
63 		super(Box(0, 0, width, height), ""d, [], null);
64 		this.app = app;
65 		PopUpMenuElement[] menuElements;
66 
67 		menuElements ~= new PopUpMenuElement("file", "File", "", [
68 			new PopUpMenuElement("new", "New project"),
69 			new PopUpMenuElement("load", "Load project"),
70 			new PopUpMenuElement("save", "Save project"),
71 			new PopUpMenuElement("saveAs", "Save project as"),
72 			new PopUpMenuElement("exit", "Exit application", "Alt + F4")
73 		]);
74 
75 		menuElements ~= new PopUpMenuElement("edit", "Edit", "", [
76 			new PopUpMenuElement("undo", "Undo"),
77 			new PopUpMenuElement("redo", "Redo"),
78 			new PopUpMenuElement("copy", "Copy"),
79 			new PopUpMenuElement("cut", "Cut"),
80 			new PopUpMenuElement("paste", "Paste")
81 		]);
82 
83 		menuElements ~= new PopUpMenuElement("view", "View", "", [
84 			new PopUpMenuElement("router", "Routing layout editor"),
85 			new PopUpMenuElement("virtmidikeyb", "Virtual MIDI keyboard"),
86 			new PopUpMenuElement("sequencer", "Sequencer")
87 		]);
88 
89 		menuElements ~= new PopUpMenuElement("audio", "Audio", "", [
90 			new PopUpMenuElement("stAudio", "Start/Stop Audio thread"),
91 			new PopUpMenuElement("cfgcompile", "Compile current configuration"),
92 		]);
93 
94 		menuElements ~= new PopUpMenuElement("help", "Help", "", [
95 			new PopUpMenuElement("helpFile", "Content"),
96 			new PopUpMenuElement("about", "About")
97 		]);
98 
99 		mb = new MenuBar("mb", Box(0, 0, width-1, 15), menuElements);
100 		addElement(mb);
101 		mb.onMenuEvent = &app.onMenuEvent;
102 	}
103 	public override void draw(bool drawHeaderOnly = false) {
104 		if(output.output.width != position.width || output.output.height != position.height)
105 			output = new BitmapDrawer(position.width(), position.height());
106 		
107 		StyleSheet ss = getStyleSheet();
108 		const Box bodyarea = Box(0, 0, position.width - 1, position.height - 1);
109 		drawFilledBox(bodyarea, ss.getColor("window"));
110 
111 		foreach (WindowElement we; elements) {
112 			we.draw();
113 		}
114 		
115 	}
116 }
117 /** 
118  * Testcase for the audio system.
119  * Capable of playing back external files.
120  */
121 public class AudioDevKit : InputListener, SystemEventListener {
122 	AudioDeviceHandler adh;
123 	ModuleManager	mm;
124 	AudioModule		selectedModule;
125 	OutputScreen	output;
126 	InputHandler	ih;
127 	Raster			mainRaster;
128 	AudioSpecs		aS;
129 	SpriteLayer		windowing;
130 	MIDIInput		midiIn;
131 	SequencerM1		midiSeq;
132 	
133 	WindowHandler	wh;
134 	Window			tlw;
135 	PresetEditor	preEdit;
136 	VirtualMidiKeyboard virtMIDIkeyb;
137 	ModuleRouter	router;
138 	ModuleConfig	mcfg;
139 	BitFlags!StateFlags	state;
140 	UndoableStack	eventStack;
141 	string			selectedModID;
142 	string			path;
143 	
144 	//ubyte[32][6][2]	level;
145 	enum StateFlags {
146 		isRunning		=	1<<0,
147 		audioThreadRunning=	1<<1,
148 		configurationCompiled=1<<2,
149 	}
150 	
151 	public this(string[] args) {
152 		state.isRunning = true;
153 		//Image fontSource = loadImage(File("../system/cp437_8x16.png"));
154 		output = new OutputScreen("PixelPerfectEngine Audio Development Kit", 848 * 2, 480 * 2);
155 		mainRaster = new Raster(848,480,output,0);
156 		windowing = new SpriteLayer(RenderingMode.Copy);
157 		//windowing.addSprite(new Bitmap8Bit(848, 480), -65_536, 0, 0);
158 		wh = new WindowHandler(1696,960,848,480,windowing);
159 		mainRaster.loadPalette(loadPaletteFromFile("../system/concreteGUIE1.tga"));
160 		mainRaster.addLayer(windowing, 0);
161 		INIT_CONCRETE();
162 		{
163 			Bitmap8Bit[] customGUIElems = loadBitmapSheetFromFile!Bitmap8Bit("../system/concreteGUI_ADK.tga", 16, 16);
164 			globalDefaultStyle.setImage(customGUIElems[6], "newA");
165 			globalDefaultStyle.setImage(customGUIElems[7], "newB");
166 			globalDefaultStyle.setImage(customGUIElems[8], "saveA");
167 			globalDefaultStyle.setImage(customGUIElems[9], "saveB");
168 			globalDefaultStyle.setImage(customGUIElems[10], "loadA");
169 			globalDefaultStyle.setImage(customGUIElems[11], "loadB");
170 			globalDefaultStyle.setImage(customGUIElems[12], "settingsA");
171 			globalDefaultStyle.setImage(customGUIElems[13], "settingsB");
172 			globalDefaultStyle.setImage(customGUIElems[14], "globalsA");
173 			globalDefaultStyle.setImage(customGUIElems[15], "globalsB");
174 			globalDefaultStyle.setImage(customGUIElems[16], "addA");
175 			globalDefaultStyle.setImage(customGUIElems[17], "addB");
176 			globalDefaultStyle.setImage(customGUIElems[18], "removeA");
177 			globalDefaultStyle.setImage(customGUIElems[19], "removeB");
178 			globalDefaultStyle.setImage(customGUIElems[20], "soloA");
179 			globalDefaultStyle.setImage(customGUIElems[21], "soloB");
180 			globalDefaultStyle.setImage(customGUIElems[22], "muteA");
181 			globalDefaultStyle.setImage(customGUIElems[23], "muteB");
182 			globalDefaultStyle.setImage(customGUIElems[24], "importA");
183 			globalDefaultStyle.setImage(customGUIElems[25], "importB");
184 			globalDefaultStyle.setImage(customGUIElems[26], "exportA");
185 			globalDefaultStyle.setImage(customGUIElems[27], "exportB");
186 			globalDefaultStyle.setImage(customGUIElems[28], "macroA");
187 			globalDefaultStyle.setImage(customGUIElems[29], "macroB");
188 		}
189 		{
190 			Bitmap8Bit[] customGUIElems = loadBitmapSheetFromFile!Bitmap8Bit("../system/concreteGUIE2.tga", 16, 16);
191 			globalDefaultStyle.setImage(customGUIElems[0], "playA");
192 			globalDefaultStyle.setImage(customGUIElems[1], "playB");
193 			globalDefaultStyle.setImage(customGUIElems[2], "stopA");
194 			globalDefaultStyle.setImage(customGUIElems[3], "stopB");
195 		}
196 
197 		ih = new InputHandler();
198 		ih.systemEventListener = this;
199 		ih.inputListener = this;
200 		ih.mouseListener = wh;
201 		WindowElement.inputHandler = ih;
202 		{
203 			import pixelperfectengine.system.input.scancode;
204 			ih.addBinding(BindingCode(ScanCode.Q, 0, Devicetype.Keyboard, 0, KeyModifier.LockKeys), 
205 					InputBinding("VirtMIDIKB-C-0"));
206 			ih.addBinding(BindingCode(ScanCode.n2, 0, Devicetype.Keyboard, 0, KeyModifier.LockKeys), 
207 					InputBinding("VirtMIDIKB-C#0"));
208 			ih.addBinding(BindingCode(ScanCode.W, 0, Devicetype.Keyboard, 0, KeyModifier.LockKeys), 
209 					InputBinding("VirtMIDIKB-D-0"));
210 			ih.addBinding(BindingCode(ScanCode.n3, 0, Devicetype.Keyboard, 0, KeyModifier.LockKeys), 
211 					InputBinding("VirtMIDIKB-D#0"));
212 			ih.addBinding(BindingCode(ScanCode.E, 0, Devicetype.Keyboard, 0, KeyModifier.LockKeys), 
213 					InputBinding("VirtMIDIKB-E-0"));
214 			ih.addBinding(BindingCode(ScanCode.R, 0, Devicetype.Keyboard, 0, KeyModifier.LockKeys), 
215 					InputBinding("VirtMIDIKB-F-0"));
216 			ih.addBinding(BindingCode(ScanCode.n5, 0, Devicetype.Keyboard, 0, KeyModifier.LockKeys), 
217 					InputBinding("VirtMIDIKB-F#0"));
218 			ih.addBinding(BindingCode(ScanCode.T, 0, Devicetype.Keyboard, 0, KeyModifier.LockKeys), 
219 					InputBinding("VirtMIDIKB-G-0"));
220 			ih.addBinding(BindingCode(ScanCode.n6, 0, Devicetype.Keyboard, 0, KeyModifier.LockKeys), 
221 					InputBinding("VirtMIDIKB-G#0"));
222 			ih.addBinding(BindingCode(ScanCode.Y, 0, Devicetype.Keyboard, 0, KeyModifier.LockKeys), 
223 					InputBinding("VirtMIDIKB-A-0"));
224 			ih.addBinding(BindingCode(ScanCode.n7, 0, Devicetype.Keyboard, 0, KeyModifier.LockKeys), 
225 					InputBinding("VirtMIDIKB-A#0"));
226 			ih.addBinding(BindingCode(ScanCode.U, 0, Devicetype.Keyboard, 0, KeyModifier.LockKeys), 
227 					InputBinding("VirtMIDIKB-B-0"));
228 			ih.addBinding(BindingCode(ScanCode.I, 0, Devicetype.Keyboard, 0, KeyModifier.LockKeys), 
229 					InputBinding("VirtMIDIKB-C-1"));
230 			ih.addBinding(BindingCode(ScanCode.n9, 0, Devicetype.Keyboard, 0, KeyModifier.LockKeys), 
231 					InputBinding("VirtMIDIKB-C#1"));
232 			ih.addBinding(BindingCode(ScanCode.O, 0, Devicetype.Keyboard, 0, KeyModifier.LockKeys), 
233 					InputBinding("VirtMIDIKB-D-1"));
234 			ih.addBinding(BindingCode(ScanCode.n0, 0, Devicetype.Keyboard, 0, KeyModifier.LockKeys), 
235 					InputBinding("VirtMIDIKB-D#1"));
236 			ih.addBinding(BindingCode(ScanCode.P, 0, Devicetype.Keyboard, 0, KeyModifier.LockKeys), 
237 					InputBinding("VirtMIDIKB-E-1"));
238 			ih.addBinding(BindingCode(ScanCode.LEFTBRACKET, 0, Devicetype.Keyboard, 0, KeyModifier.LockKeys), 
239 					InputBinding("VirtMIDIKB-F-1"));
240 			ih.addBinding(BindingCode(ScanCode.EQUALS, 0, Devicetype.Keyboard, 0, KeyModifier.LockKeys), 
241 					InputBinding("VirtMIDIKB-F#1"));
242 			ih.addBinding(BindingCode(ScanCode.RIGHTBRACKET, 0, Devicetype.Keyboard, 0, KeyModifier.LockKeys), 
243 					InputBinding("VirtMIDIKB-G-1"));
244 			ih.addBinding(BindingCode(ScanCode.HOME, 0, Devicetype.Keyboard, 0, KeyModifier.LockKeys), 
245 					InputBinding("VirtMIDIKB-oct+"));
246 			ih.addBinding(BindingCode(ScanCode.END, 0, Devicetype.Keyboard, 0, KeyModifier.LockKeys), 
247 					InputBinding("VirtMIDIKB-oct-"));
248 			ih.addBinding(BindingCode(ScanCode.PAGEUP, 0, Devicetype.Keyboard, 0, KeyModifier.LockKeys), 
249 					InputBinding("VirtMIDIKB-note+"));
250 			ih.addBinding(BindingCode(ScanCode.PAGEDOWN, 0, Devicetype.Keyboard, 0, KeyModifier.LockKeys), 
251 					InputBinding("VirtMIDIKB-note-"));
252 		}
253 
254 		AudioDeviceHandler.initAudioDriver(OS_PREFERRED_DRIVER);
255 	
256 		initMIDI();
257 		Bitmap4Bit background = new Bitmap4Bit(848, 480);
258 		wh.addBackground(background);
259 		wh.addWindow(new AudioConfig(this));
260 		eventStack = new UndoableStack(10);
261 	}
262 	void whereTheMagicHappens() {
263 		while (state.isRunning) {
264 			mainRaster.refresh();
265 			ih.test();
266 			timer.test();
267 		}
268 		if (mm !is null) {
269 			synchronized
270 				writeln(mm.suspendAudioThread());
271 		}
272 	}
273 	public void onStart() {
274 		tlw = new TopLevelWindow(848, 480, this);
275 		wh.setBaseWindow(tlw);
276 		mcfg = new ModuleConfig(mm);
277 	}
278 	public void onMenuEvent(Event ev) {
279 		MenuEvent me = cast(MenuEvent)ev;
280 		switch (me.itemSource) {
281 			case "preEdit":
282 				openPresetEditor();
283 				break;
284 			case "router":
285 				openRouter();
286 				break;
287 			case "stAudio":
288 				onAudioThreadSwitch();
289 				break;
290 			case "cfgcompile":
291 				onCompileAudioConfig();
292 				break;
293 			case "exit":
294 				state.isRunning = false;
295 				break;
296 			case "new":
297 				onNew();
298 				break;
299 			case "load":
300 				onLoad();
301 				break;
302 			case "save":
303 				onSave();
304 				break;
305 			case "saveAs":
306 				onSaveAs();
307 				break;
308 			case "virtmidikeyb":
309 				onVirtMIDIKeyb();
310 				break;
311 			case "sequencer":
312 				openSequencer();
313 				break;
314 			default: break;
315 		}
316 	}
317 	public void onVirtMIDIKeyb() {
318 		if (virtMIDIkeyb is null) {
319 			virtMIDIkeyb = new VirtualMidiKeyboard(this);
320 			wh.addWindow(virtMIDIkeyb);
321 		}
322 	}
323 	public void onVirtMIDIKeybClose() {
324 		virtMIDIkeyb = null;
325 	}
326 	public void onAudioThreadSwitch() {
327 		if (state.audioThreadRunning) {
328 			const int errorCode = mm.suspendAudioThread();
329 			state.audioThreadRunning = false;
330 			if (errorCode) {
331 				wh.message("Audio thread error!", "An error occured during audio thread runtime!\nError code:" ~ 
332 						errorCode.to!dstring);
333 			}
334 		} else {
335 			const int errorCode = mm.runAudioThread();
336 			if (!errorCode) {
337 				state.audioThreadRunning = true;
338 			} else {
339 				wh.message("Audio thread error!", "Failed to initialize audio thread!\nError code:" ~ errorCode.to!dstring);
340 			}
341 		}
342 	}
343 	public void onCompileAudioConfig() {
344 		try {
345 			mcfg.compile(state.audioThreadRunning);
346 			if (mcfg.midiRouting.length) {
347 				midiSeq = new SequencerM1(mcfg.modules, mcfg.midiRouting, mcfg.midiGroups);
348 				mm.midiSeq = midiSeq;
349 			} else {
350 				midiSeq = null;
351 			}
352 		} catch (Exception e) {
353 			writeln(e);
354 		}
355 	}
356 	public void onNew() {
357 		mcfg = new ModuleConfig(mm);
358 	}
359 	public void onLoad() {
360 		import pixelperfectengine.concrete.dialogs.filedialog;
361 		wh.addWindow(new FileDialog("Load audio configuration file.", "loadConfigDialog", &onLoadConfigurationFile, 
362 			[FileDialog.FileAssociationDescriptor("SDLang file", ["*.sdl"])], "./"));
363 	}
364 	public void onLoadConfigurationFile(Event ev) {
365 		FileEvent fe = cast(FileEvent)ev;
366 		path = fe.getFullPath;
367 		File f = File(path);
368 		char[] c;
369 		c.length = cast(size_t)f.size();
370 		f.rawRead(c);
371 		mcfg.loadConfig(c.idup);
372 		if (router !is null) {
373 			router.refreshRoutingTable();
374 			router.refreshModuleList();
375 		}
376 	}
377 	public void onSave() {
378 		if (!path.length) {
379 			onSaveAs();
380 		} else {
381 			try {
382 				mcfg.save(path);
383 			} catch(Exception e) {
384 				debug writeln(e);
385 			}
386 		}
387 	}
388 	public void onSaveAs() {
389 		import pixelperfectengine.concrete.dialogs.filedialog;
390 		wh.addWindow(new FileDialog("Save audio configuration file.", "saveConfigDialog", &onSaveConfigurationFile, 
391 			[FileDialog.FileAssociationDescriptor("SDLang file", ["*.sdl"])], "./", true));
392 	}
393 	public void onSaveConfigurationFile(Event ev) {
394 		FileEvent fe = cast(FileEvent)ev;
395 		path = fe.getFullPath;
396 		try {
397 			mcfg.save(path);
398 		} catch(Exception e) {
399 			debug writeln(e);
400 		}
401 	}
402 	public void openSequencer() {
403 		if (midiSeq !is null) {
404 			wh.addWindow(new SequencerCtrl(this));
405 		}
406 	}
407 	public void onMIDILoad() {
408 		import pixelperfectengine.concrete.dialogs.filedialog;
409 		wh.addWindow(new FileDialog("Load MIDI file.", "loadMidiDialog", &onMIDIFileLoad, 
410 			[FileDialog.FileAssociationDescriptor("MIDI file", ["*.mid"])], "./"));
411 	}
412 	public void onMIDIFileLoad(Event ev) {
413 		import mididi;
414 		FileEvent fe = cast(FileEvent)ev;
415 		midiSeq.openMIDI(readMIDIFile(fe.getFullPath));
416 		midiSeq.reset();
417 	}
418 	public void openRouter() {
419 		if (router is null)
420 			router = new ModuleRouter(this);
421 		if (wh.whichWindow(router) == -1)
422 			wh.addWindow(router);
423 		
424 	}
425 	public void openPresetEditor() {
426 		if (selectedModule !is null)
427 			wh.addWindow(new PresetEditor(this));
428 	}
429 	public void keyEvent(uint id, BindingCode code, uint timestamp, bool isPressed) {
430 		if (virtMIDIkeyb !is null) {
431 			if (virtMIDIkeyb.keyEventReceive(id, code, timestamp, isPressed))
432 				return;
433 		}
434 	}
435 	public void midiInCallback(ubyte[] data, size_t timestamp) @nogc nothrow {
436 		if (selectedModule !is null) {
437 			UMP msb = UMP(MessageType.MIDI1, 0, 0, 0);
438 			msb.bytes[1] = data.length > 0 ? data[0] : 0;
439 			msb.bytes[2] = data.length > 1 ? data[1] : 0;
440 			msb.bytes[3] = data.length > 2 ? data[2] : 0;
441 			selectedModule.midiReceive(msb);
442 		}
443 	}
444 	public void axisEvent(uint id, BindingCode code, uint timestamp, float value) {
445 		
446 	}
447 	
448 	public void onQuit() {
449 		state.isRunning = false;
450 	}
451 	
452 	public void controllerAdded(uint id) {
453 		
454 	}
455 	
456 	public void controllerRemoved(uint id) {
457 		
458 	}
459 	
460 }