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 }