changeset 149:0aa18fe8f7f1

add: base code structure for cross-platform sound
author Sam <sam@basx.dev>
date Thu, 27 Apr 2023 00:36:45 +0700
parents ae8b0d89a2ee
children 7ed6f87a0fe1
files src/semicongine/audio.nim src/semicongine/platform/audio.nim src/semicongine/platform/linux/audio.nim src/semicongine/platform/linux/window.nim src/semicongine/platform/linux/xlib.nim src/semicongine/platform/window.nim src/semicongine/platform/windows/audio.nim src/semicongine/platform/windows/win32.nim src/semicongine/platform/windows/window.nim
diffstat 6 files changed, 273 insertions(+), 265 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/semicongine/platform/audio.nim	Thu Apr 27 00:36:45 2023 +0700
@@ -0,0 +1,6 @@
+when defined(linux):
+  include ./linux/audio
+elif defined(windows):
+  include ./windows/audio
+
+export audio
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/semicongine/platform/linux/window.nim	Thu Apr 27 00:36:45 2023 +0700
@@ -0,0 +1,149 @@
+import std/options
+import std/tables
+import
+  x11/xlib,
+  x11/xutil,
+  x11/keysym
+import x11/x
+
+import ../../events
+import ../../math/vector
+
+import ./symkey_map
+
+export keysym
+
+var deleteMessage*: Atom
+
+type
+  NativeWindow* = object
+    display*: ptr xlib.Display
+    window*: x.Window
+    emptyCursor: Cursor
+
+template checkXlibResult*(call: untyped) =
+  let value = call
+  if value == 0:
+    raise newException(Exception, "Xlib error: " & astToStr(call) &
+        " returned " & $value)
+
+proc createWindow*(title: string): NativeWindow =
+  checkXlibResult XInitThreads()
+  let display = XOpenDisplay(nil)
+  if display == nil:
+    quit "Failed to open display"
+
+  let
+    screen = XDefaultScreen(display)
+    rootWindow = XRootWindow(display, screen)
+    foregroundColor = XBlackPixel(display, screen)
+    backgroundColor = XWhitePixel(display, screen)
+
+  let window = XCreateSimpleWindow(display, rootWindow, -1, -1, 800, 600, 0,
+      foregroundColor, backgroundColor)
+  checkXlibResult XSetStandardProperties(display, window, title, "window", 0,
+      nil, 0, nil)
+  checkXlibResult XSelectInput(display, window, PointerMotionMask or
+      ButtonPressMask or ButtonReleaseMask or KeyPressMask or KeyReleaseMask or ExposureMask)
+  checkXlibResult XMapWindow(display, window)
+
+  deleteMessage = XInternAtom(display, "WM_DELETE_WINDOW", XBool(false))
+  checkXlibResult XSetWMProtocols(display, window, addr(deleteMessage), 1)
+
+  # quite a lot of work to hide the cursor...
+  var data = "\0".cstring
+  var pixmap = XCreateBitmapFromData(display, window, data, 1, 1)
+  var color: XColor
+  var empty_cursor = XCreatePixmapCursor(display, pixmap, pixmap, addr(color),
+      addr(color), 0, 0)
+  checkXlibResult XFreePixmap(display, pixmap)
+  checkXlibResult XDefineCursor(display, window, empty_cursor)
+
+  return NativeWindow(display: display, window: window,
+      emptyCursor: empty_cursor)
+
+proc destroy*(window: NativeWindow) =
+  checkXlibResult window.display.XFreeCursor(window.emptyCursor)
+  checkXlibResult window.display.XDestroyWindow(window.window)
+  discard window.display.XCloseDisplay() # always returns 0
+
+proc size*(window: NativeWindow): (int, int) =
+  var attribs: XWindowAttributes
+  checkXlibResult XGetWindowAttributes(window.display, window.window, addr(attribs))
+  return (int(attribs.width), int(attribs.height))
+
+proc pendingEvents*(window: NativeWindow): seq[Event] =
+  var
+    event: XEvent
+    serials: Table[culong, Table[int, seq[Event]]]
+  while window.display.XPending() > 0:
+    discard window.display.XNextEvent(addr(event))
+    case event.theType
+    of ClientMessage:
+      if cast[Atom](event.xclient.data.l[0]) == deleteMessage:
+        result.add(Event(eventType: Quit))
+    of KeyPress:
+      let keyevent = cast[PXKeyEvent](addr(event))
+      let xkey = int(keyevent.keycode)
+      # ugly, but required to catch auto-repeat keys of X11
+      if not (keyevent.serial in serials):
+        serials[keyevent.serial] = initTable[int, seq[Event]]()
+      if not (xkey in serials[keyevent.serial]):
+        serials[keyevent.serial][xkey] = newSeq[Event]()
+      serials[keyevent.serial][xkey].add(Event(eventType: KeyPressed,
+          key: KeyTypeMap.getOrDefault(xkey, Key.UNKNOWN)))
+    of KeyRelease:
+      let keyevent = cast[PXKeyEvent](addr(event))
+      let xkey = int(keyevent.keycode)
+      # ugly, but required to catch auto-repeat keys of X11
+      if not (keyevent.serial in serials):
+        serials[keyevent.serial] = initTable[int, seq[Event]]()
+      if not (xkey in serials[keyevent.serial]):
+        serials[keyevent.serial][xkey] = newSeq[Event]()
+      serials[keyevent.serial][xkey].add Event(eventType: KeyReleased,
+          key: KeyTypeMap.getOrDefault(xkey, Key.UNKNOWN))
+    of ButtonPress:
+      let button = int(cast[PXButtonEvent](addr(event)).button)
+      result.add Event(eventType: MousePressed,
+          button: MouseButtonTypeMap.getOrDefault(button, MouseButton.UNKNOWN))
+    of ButtonRelease:
+      let button = int(cast[PXButtonEvent](addr(event)).button)
+      result.add Event(eventType: MouseReleased,
+          button: MouseButtonTypeMap.getOrDefault(button, MouseButton.UNKNOWN))
+    of MotionNotify:
+      let motion = cast[PXMotionEvent](addr(event))
+      result.add Event(eventType: MouseMoved, x: motion.x, y: motion.y)
+    of ConfigureNotify, Expose:
+      result.add Event(eventType: ResizedWindow)
+    else:
+      discard
+  # little hack to work around X11 auto-repeat keys
+  for (serial, keys) in serials.pairs:
+    for (key, events) in keys.pairs:
+      if events.len == 1:
+        result.add events[0]
+
+
+proc getMousePosition*(window: NativeWindow): Option[Vec2f] =
+  var
+    root: x.Window
+    win: x.Window
+    rootX: cint
+    rootY: cint
+    winX: cint
+    winY: cint
+    mask: cuint
+    onscreen = XQueryPointer(
+      window.display,
+      window.window,
+      addr(root),
+      addr(win),
+      addr(rootX),
+      addr(rootY),
+      addr(winX),
+      addr(winY),
+      addr(mask),
+    )
+  if onscreen != 0:
+    result = some(Vec2f([float32(winX), float32(winY)]))
+
--- a/src/semicongine/platform/linux/xlib.nim	Thu Apr 27 00:30:19 2023 +0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,149 +0,0 @@
-import std/options
-import std/tables
-import
-  x11/xlib,
-  x11/xutil,
-  x11/keysym
-import x11/x
-
-import ../../events
-import ../../math/vector
-
-import ./symkey_map
-
-export keysym
-
-var deleteMessage*: Atom
-
-type
-  NativeWindow* = object
-    display*: ptr xlib.Display
-    window*: x.Window
-    emptyCursor: Cursor
-
-template checkXlibResult*(call: untyped) =
-  let value = call
-  if value == 0:
-    raise newException(Exception, "Xlib error: " & astToStr(call) &
-        " returned " & $value)
-
-proc createWindow*(title: string): NativeWindow =
-  checkXlibResult XInitThreads()
-  let display = XOpenDisplay(nil)
-  if display == nil:
-    quit "Failed to open display"
-
-  let
-    screen = XDefaultScreen(display)
-    rootWindow = XRootWindow(display, screen)
-    foregroundColor = XBlackPixel(display, screen)
-    backgroundColor = XWhitePixel(display, screen)
-
-  let window = XCreateSimpleWindow(display, rootWindow, -1, -1, 800, 600, 0,
-      foregroundColor, backgroundColor)
-  checkXlibResult XSetStandardProperties(display, window, title, "window", 0,
-      nil, 0, nil)
-  checkXlibResult XSelectInput(display, window, PointerMotionMask or
-      ButtonPressMask or ButtonReleaseMask or KeyPressMask or KeyReleaseMask or ExposureMask)
-  checkXlibResult XMapWindow(display, window)
-
-  deleteMessage = XInternAtom(display, "WM_DELETE_WINDOW", XBool(false))
-  checkXlibResult XSetWMProtocols(display, window, addr(deleteMessage), 1)
-
-  # quite a lot of work to hide the cursor...
-  var data = "\0".cstring
-  var pixmap = XCreateBitmapFromData(display, window, data, 1, 1)
-  var color: XColor
-  var empty_cursor = XCreatePixmapCursor(display, pixmap, pixmap, addr(color),
-      addr(color), 0, 0)
-  checkXlibResult XFreePixmap(display, pixmap)
-  checkXlibResult XDefineCursor(display, window, empty_cursor)
-
-  return NativeWindow(display: display, window: window,
-      emptyCursor: empty_cursor)
-
-proc destroy*(window: NativeWindow) =
-  checkXlibResult window.display.XFreeCursor(window.emptyCursor)
-  checkXlibResult window.display.XDestroyWindow(window.window)
-  discard window.display.XCloseDisplay() # always returns 0
-
-proc size*(window: NativeWindow): (int, int) =
-  var attribs: XWindowAttributes
-  checkXlibResult XGetWindowAttributes(window.display, window.window, addr(attribs))
-  return (int(attribs.width), int(attribs.height))
-
-proc pendingEvents*(window: NativeWindow): seq[Event] =
-  var
-    event: XEvent
-    serials: Table[culong, Table[int, seq[Event]]]
-  while window.display.XPending() > 0:
-    discard window.display.XNextEvent(addr(event))
-    case event.theType
-    of ClientMessage:
-      if cast[Atom](event.xclient.data.l[0]) == deleteMessage:
-        result.add(Event(eventType: Quit))
-    of KeyPress:
-      let keyevent = cast[PXKeyEvent](addr(event))
-      let xkey = int(keyevent.keycode)
-      # ugly, but required to catch auto-repeat keys of X11
-      if not (keyevent.serial in serials):
-        serials[keyevent.serial] = initTable[int, seq[Event]]()
-      if not (xkey in serials[keyevent.serial]):
-        serials[keyevent.serial][xkey] = newSeq[Event]()
-      serials[keyevent.serial][xkey].add(Event(eventType: KeyPressed,
-          key: KeyTypeMap.getOrDefault(xkey, Key.UNKNOWN)))
-    of KeyRelease:
-      let keyevent = cast[PXKeyEvent](addr(event))
-      let xkey = int(keyevent.keycode)
-      # ugly, but required to catch auto-repeat keys of X11
-      if not (keyevent.serial in serials):
-        serials[keyevent.serial] = initTable[int, seq[Event]]()
-      if not (xkey in serials[keyevent.serial]):
-        serials[keyevent.serial][xkey] = newSeq[Event]()
-      serials[keyevent.serial][xkey].add Event(eventType: KeyReleased,
-          key: KeyTypeMap.getOrDefault(xkey, Key.UNKNOWN))
-    of ButtonPress:
-      let button = int(cast[PXButtonEvent](addr(event)).button)
-      result.add Event(eventType: MousePressed,
-          button: MouseButtonTypeMap.getOrDefault(button, MouseButton.UNKNOWN))
-    of ButtonRelease:
-      let button = int(cast[PXButtonEvent](addr(event)).button)
-      result.add Event(eventType: MouseReleased,
-          button: MouseButtonTypeMap.getOrDefault(button, MouseButton.UNKNOWN))
-    of MotionNotify:
-      let motion = cast[PXMotionEvent](addr(event))
-      result.add Event(eventType: MouseMoved, x: motion.x, y: motion.y)
-    of ConfigureNotify, Expose:
-      result.add Event(eventType: ResizedWindow)
-    else:
-      discard
-  # little hack to work around X11 auto-repeat keys
-  for (serial, keys) in serials.pairs:
-    for (key, events) in keys.pairs:
-      if events.len == 1:
-        result.add events[0]
-
-
-proc getMousePosition*(window: NativeWindow): Option[Vec2f] =
-  var
-    root: x.Window
-    win: x.Window
-    rootX: cint
-    rootY: cint
-    winX: cint
-    winY: cint
-    mask: cuint
-    onscreen = XQueryPointer(
-      window.display,
-      window.window,
-      addr(root),
-      addr(win),
-      addr(rootX),
-      addr(rootY),
-      addr(winX),
-      addr(winY),
-      addr(mask),
-    )
-  if onscreen != 0:
-    result = some(Vec2f([float32(winX), float32(winY)]))
-
--- a/src/semicongine/platform/window.nim	Thu Apr 27 00:30:19 2023 +0700
+++ b/src/semicongine/platform/window.nim	Thu Apr 27 00:36:45 2023 +0700
@@ -1,4 +1,6 @@
 when defined(linux):
-  include ./linux/xlib
+  include ./linux/window
 elif defined(windows):
-  include ./windows/win32
+  include ./windows/window
+
+export window
--- a/src/semicongine/platform/windows/win32.nim	Thu Apr 27 00:30:19 2023 +0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,114 +0,0 @@
-import std/options
-import winim
-
-import ./virtualkey_map
-import ../../events
-import ../../math/vector
-
-type
-  NativeWindow* = object
-    hinstance*: HINSTANCE
-    hwnd*: HWND
-
-# sorry, have to use module-global variable to capture windows events
-var currentEvents: seq[Event]
-
-template checkWin32Result*(call: untyped) =
-  let value = call
-  if value == 0:
-    raise newException(Exception, "Win32 error: " & astToStr(call) & " returned " & $value)
-
-
-proc MapLeftRightKeys(key: INT, lparam: LPARAM): INT =
-  case key
-  of VK_SHIFT:
-    MapVirtualKey(UINT((lParam and 0x00ff0000) shr 16), MAPVK_VSC_TO_VK_EX)
-  of VK_CONTROL:
-    if (lParam and 0x01000000) == 0: VK_LCONTROL else: VK_RCONTROL
-  of VK_MENU:
-    if (lParam and 0x01000000) == 0: VK_LMENU else: VK_RMENU
-  else:
-    key
-
-proc WindowHandler(hwnd: HWND, uMsg: UINT, wParam: WPARAM, lParam: LPARAM): LRESULT {.stdcall.} =
-  case uMsg
-  of WM_DESTROY:
-    currentEvents.add(Event(eventType: events.EventType.Quit))
-  of WM_KEYDOWN, WM_SYSKEYDOWN:
-    let key = MapLeftRightKeys(INT(wParam), lParam)
-    currentEvents.add(Event(eventType: KeyPressed, key: KeyTypeMap.getOrDefault(key, Key.UNKNOWN)))
-  of WM_KEYUP, WM_SYSKEYUP:
-    let key = MapLeftRightKeys(INT(wParam), lParam)
-    currentEvents.add(Event(eventType: KeyReleased, key: KeyTypeMap.getOrDefault(key, Key.UNKNOWN)))
-  of WM_LBUTTONDOWN:
-    currentEvents.add(Event(eventType: MousePressed, button: MouseButton.Mouse1))
-  of WM_LBUTTONUP:
-    currentEvents.add(Event(eventType: MouseReleased, button: MouseButton.Mouse1))
-  of WM_MBUTTONDOWN:
-    currentEvents.add(Event(eventType: MousePressed, button: MouseButton.Mouse2))
-  of WM_MBUTTONUP:
-    currentEvents.add(Event(eventType: MouseReleased, button: MouseButton.Mouse2))
-  of WM_RBUTTONDOWN:
-    currentEvents.add(Event(eventType: MousePressed, button: MouseButton.Mouse3))
-  of WM_RBUTTONUP:
-    currentEvents.add(Event(eventType: MouseReleased, button: MouseButton.Mouse3))
-  of WM_MOUSEMOVE:
-    currentEvents.add(Event(eventType: events.MouseMoved, x: GET_X_LPARAM(lParam), y: GET_Y_LPARAM(lParam)))
-  else:
-    return DefWindowProc(hwnd, uMsg, wParam, lParam)
-
-
-proc createWindow*(title: string): NativeWindow =
-  result.hInstance = HINSTANCE(GetModuleHandle(nil))
-  var
-    windowClassName = T"EngineWindowClass"
-    windowName = T(title)
-    windowClass = WNDCLASSEX(
-      cbSize: UINT(WNDCLASSEX.sizeof),
-      lpfnWndProc: WindowHandler,
-      hInstance: result.hInstance,
-      lpszClassName: windowClassName,
-    )
-  
-  if(RegisterClassEx(addr(windowClass)) == 0):
-    raise newException(Exception, "Unable to register window class")
-
-  result.hwnd = CreateWindowEx(
-      DWORD(0),
-      windowClassName,
-      windowName,
-      DWORD(WS_OVERLAPPEDWINDOW),
-      CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
-      HMENU(0),
-      HINSTANCE(0),
-      result.hInstance,
-      nil
-    )
-
-  discard ShowWindow(result.hwnd, SW_SHOW)
-  discard ShowCursor(false)
-
-proc destroy*(window: NativeWindow) =
-  discard
-
-proc size*(window: NativeWindow): (int, int) =
-  var rect: RECT
-  checkWin32Result GetWindowRect(window.hwnd, addr(rect))
-  (int(rect.right - rect.left), int(rect.bottom - rect.top))
-
-proc pendingEvents*(window: NativeWindow): seq[Event] =
-  # empty queue
-  currentEvents = newSeq[Event]()
-  var msg: MSG
-  # fill queue
-  while PeekMessage(addr(msg), window.hwnd, 0, 0, PM_REMOVE):
-    TranslateMessage(addr(msg))
-    DispatchMessage(addr(msg))
-  return currentEvents
-
-proc getMousePosition*(window: NativeWindow): Option[Vec2f] =
-  var p: POINT
-  let res = GetCursorPos(addr(p))
-  if res:
-    return some(Vec2f([float32(p.x), float32(p.y)]))
-  return none(Vec2f)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/semicongine/platform/windows/window.nim	Thu Apr 27 00:36:45 2023 +0700
@@ -0,0 +1,114 @@
+import std/options
+import winim
+
+import ./virtualkey_map
+import ../../events
+import ../../math/vector
+
+type
+  NativeWindow* = object
+    hinstance*: HINSTANCE
+    hwnd*: HWND
+
+# sorry, have to use module-global variable to capture windows events
+var currentEvents: seq[Event]
+
+template checkWin32Result*(call: untyped) =
+  let value = call
+  if value == 0:
+    raise newException(Exception, "Win32 error: " & astToStr(call) & " returned " & $value)
+
+
+proc MapLeftRightKeys(key: INT, lparam: LPARAM): INT =
+  case key
+  of VK_SHIFT:
+    MapVirtualKey(UINT((lParam and 0x00ff0000) shr 16), MAPVK_VSC_TO_VK_EX)
+  of VK_CONTROL:
+    if (lParam and 0x01000000) == 0: VK_LCONTROL else: VK_RCONTROL
+  of VK_MENU:
+    if (lParam and 0x01000000) == 0: VK_LMENU else: VK_RMENU
+  else:
+    key
+
+proc WindowHandler(hwnd: HWND, uMsg: UINT, wParam: WPARAM, lParam: LPARAM): LRESULT {.stdcall.} =
+  case uMsg
+  of WM_DESTROY:
+    currentEvents.add(Event(eventType: events.EventType.Quit))
+  of WM_KEYDOWN, WM_SYSKEYDOWN:
+    let key = MapLeftRightKeys(INT(wParam), lParam)
+    currentEvents.add(Event(eventType: KeyPressed, key: KeyTypeMap.getOrDefault(key, Key.UNKNOWN)))
+  of WM_KEYUP, WM_SYSKEYUP:
+    let key = MapLeftRightKeys(INT(wParam), lParam)
+    currentEvents.add(Event(eventType: KeyReleased, key: KeyTypeMap.getOrDefault(key, Key.UNKNOWN)))
+  of WM_LBUTTONDOWN:
+    currentEvents.add(Event(eventType: MousePressed, button: MouseButton.Mouse1))
+  of WM_LBUTTONUP:
+    currentEvents.add(Event(eventType: MouseReleased, button: MouseButton.Mouse1))
+  of WM_MBUTTONDOWN:
+    currentEvents.add(Event(eventType: MousePressed, button: MouseButton.Mouse2))
+  of WM_MBUTTONUP:
+    currentEvents.add(Event(eventType: MouseReleased, button: MouseButton.Mouse2))
+  of WM_RBUTTONDOWN:
+    currentEvents.add(Event(eventType: MousePressed, button: MouseButton.Mouse3))
+  of WM_RBUTTONUP:
+    currentEvents.add(Event(eventType: MouseReleased, button: MouseButton.Mouse3))
+  of WM_MOUSEMOVE:
+    currentEvents.add(Event(eventType: events.MouseMoved, x: GET_X_LPARAM(lParam), y: GET_Y_LPARAM(lParam)))
+  else:
+    return DefWindowProc(hwnd, uMsg, wParam, lParam)
+
+
+proc createWindow*(title: string): NativeWindow =
+  result.hInstance = HINSTANCE(GetModuleHandle(nil))
+  var
+    windowClassName = T"EngineWindowClass"
+    windowName = T(title)
+    windowClass = WNDCLASSEX(
+      cbSize: UINT(WNDCLASSEX.sizeof),
+      lpfnWndProc: WindowHandler,
+      hInstance: result.hInstance,
+      lpszClassName: windowClassName,
+    )
+  
+  if(RegisterClassEx(addr(windowClass)) == 0):
+    raise newException(Exception, "Unable to register window class")
+
+  result.hwnd = CreateWindowEx(
+      DWORD(0),
+      windowClassName,
+      windowName,
+      DWORD(WS_OVERLAPPEDWINDOW),
+      CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
+      HMENU(0),
+      HINSTANCE(0),
+      result.hInstance,
+      nil
+    )
+
+  discard ShowWindow(result.hwnd, SW_SHOW)
+  discard ShowCursor(false)
+
+proc destroy*(window: NativeWindow) =
+  discard
+
+proc size*(window: NativeWindow): (int, int) =
+  var rect: RECT
+  checkWin32Result GetWindowRect(window.hwnd, addr(rect))
+  (int(rect.right - rect.left), int(rect.bottom - rect.top))
+
+proc pendingEvents*(window: NativeWindow): seq[Event] =
+  # empty queue
+  currentEvents = newSeq[Event]()
+  var msg: MSG
+  # fill queue
+  while PeekMessage(addr(msg), window.hwnd, 0, 0, PM_REMOVE):
+    TranslateMessage(addr(msg))
+    DispatchMessage(addr(msg))
+  return currentEvents
+
+proc getMousePosition*(window: NativeWindow): Option[Vec2f] =
+  var p: POINT
+  let res = GetCursorPos(addr(p))
+  if res:
+    return some(Vec2f([float32(p.x), float32(p.y)]))
+  return none(Vec2f)