update pyglet and add more

This commit is contained in:
shenjackyuanjie 2022-06-04 11:08:30 +08:00
parent 9288423d54
commit e2b0109216
43 changed files with 660 additions and 699 deletions

2
.gitattributes vendored
View File

@ -1,2 +0,0 @@
# Auto detect text files and perform LF normalization
* text=auto

View File

@ -18,19 +18,17 @@ __all__ = ['TexturesError',
class Error(Exception):
"""基础 Exception"""
pass
def __bool__(self):
return False
class TexturesError(Error):
"""材质相关 error"""
pass
class LanguageError(Error):
"""语言相关 error"""
pass
class TestError(Error):
"""就像名字一样 用于测试的 error"""
pass

View File

@ -22,11 +22,26 @@ class CommandParseError(CommandError):
"""命令解析时出现错误"""
class CommandQuotationMarkPositionError(CommandParseError):
# QMark -> Quotation marks
# Pos -> Position
class CommandQMarkPosError(CommandParseError):
"""命令中,引号位置不正确
例如 /command "aabcc "awdawd"""
class CommandQuotationMarkMissing(CommandParseError):
class CommandQMarkMissing(CommandParseError):
"""命令中引号缺失
例如: /command "aawwdawda awdaw """
class CommandQMarkPreMissing(CommandQMarkMissing):
"""命令中 前面的引号缺失
例如: /command aaaa" aaaaaa"""
suf_qmark_pos = None
class CommandQMarkSufMissing(CommandQMarkMissing):
"""命令中 后面的引号缺失(引号未闭合)
例如: /command "aaaawaa some command"""
pre_qmark_pos = None

View File

@ -184,6 +184,7 @@ class ClientWindow(Window):
config_file['window']['width'] = self.width
config_file['window']['height'] = self.height
toml.dump(config_file, open('./configs/main.toml', 'w'))
self.logger.info('save_info end')
"""
draws and some event
@ -195,9 +196,10 @@ class ClientWindow(Window):
self.fps_log.update_tick(Decimal(tick))
def FPS_update(self, tick: Decimal):
now_FPS = pyglet.clock.get_fps()
now_FPS = pyglet.clock.get_frequency()
self.fps_log.update_tick(tick)
self.fps_label.text = f'FPS: {self.fps_log.fps: >5.1f}({self.fps_log.middle_fps: >5.1f})[{now_FPS}]\n {self.fps_log.max_fps: >7.1f} {self.fps_log.min_fps:>5.1f}'
self.fps_label.text = f'FPS: {self.fps_log.fps: >5.1f}({self.fps_log.middle_fps: >5.1f})[{now_FPS: >.7f}]\n {self.fps_log.max_fps: >7.1f} {self.fps_log.min_fps:>5.1f}'
def on_draw(self, *dt):
# self.logger.debug('on_draw call dt: {}'.format(dt))

View File

@ -17,8 +17,7 @@ import statistics
from typing import Union
from decimal import Decimal
from libs.pyglet import clock
from libs.pyglet.clock import get_frequency
class FpsLogger:
def __init__(self,
@ -36,7 +35,7 @@ class FpsLogger:
def update_tick(self,
tick: Decimal):
now_fps = clock.get_fps()
now_fps = get_frequency()
if now_fps != 0:
self.fps_list.append(now_fps)
else:

View File

@ -39,13 +39,22 @@ class CommandText:
self.tree_node = tree_list
@staticmethod
def parse_command(raw_command: Union[str, "CommandText"]) -> Tuple[list, Optional[Type[CommandParseError]]]:
def parse_command(raw_command: Union[str, "CommandText"]) -> Tuple[list, Union[Type[CommandParseError], type(True)]]:
spilt_list = str(raw_command).split(" ")
for spilted in spilt_list:
pass
spilts = [None, None]
for spited in spilt_list:
if len(spited) > 1:
if spited[0] == "\"": # 开头有一个 "
if spilts[0] is None: # 如果没有标记一个字符串开头
pass
else: # 已经标记了一个字符串开头
return spilt_list, CommandQMarkPosError
if spited[-1] == "\"" and spited[-2] != "\\": # 末尾有一个没有被转义的 "
return spilt_list, CommandQuotationMarkPositionError
...
return spilt_list, True
def find(self, text: str) -> Union[str, bool]:
finding = re.match(text, self.text)

2
desktop.ini Normal file
View File

@ -0,0 +1,2 @@
[.ShellClassInfo]
IconResource=C:\WINDOWS\System32\SHELL32.dll,11

View File

@ -44,7 +44,7 @@ import sys
from typing import TYPE_CHECKING
#: The release version
version = '2.0.dev14'
version = '2.0.dev18'
__version__ = version
if sys.version_info < (3, 6):
@ -360,7 +360,6 @@ if TYPE_CHECKING:
from . import app
from . import canvas
from . import clock
from . import com
from . import event
from . import font
from . import gl
@ -381,7 +380,6 @@ else:
app = _ModuleProxy('app')
canvas = _ModuleProxy('canvas')
clock = _ModuleProxy('clock')
com = _ModuleProxy('com')
event = _ModuleProxy('event')
font = _ModuleProxy('font')
gl = _ModuleProxy('gl')

View File

@ -369,7 +369,7 @@ class Clock:
return None
def get_fps(self):
def get_frequency(self):
"""Get the average clock update frequency of recent history.
The result is the average of a sliding window of the last "n" updates,
@ -638,7 +638,7 @@ def get_sleep_time(sleep_idle):
return _default.get_sleep_time(sleep_idle)
def get_fps():
def get_frequency():
"""Get the average clock update frequency.
The result is the sliding average of the last "n" updates,
@ -650,7 +650,7 @@ def get_fps():
:rtype: float
:return: The measured updates per second.
"""
return _default.get_fps()
return _default.get_frequency()
def schedule(func, *args, **kwargs):

View File

@ -9,7 +9,7 @@ from pyglet.image.codecs.wic import IWICBitmap, GUID_WICPixelFormat32bppBGR, WIC
from pyglet import image
import ctypes
import math
from pyglet import com
from pyglet.libs.win32 import com
from pyglet.libs.win32 import _kernel32 as kernel32
from pyglet.libs.win32 import _ole32 as ole32
from pyglet.libs.win32.constants import *

View File

@ -227,7 +227,7 @@ class Win32Font(base.Font):
super(Win32Font, self).__init__()
self.logfont = self.get_logfont(name, size, bold, italic, dpi)
self.hfont = gdi32.CreateFontIndirectA(byref(self.logfont))
self.hfont = gdi32.CreateFontIndirectW(byref(self.logfont))
# Create a dummy DC for coordinate mapping
dc = user32.GetDC(0)
@ -250,7 +250,7 @@ class Win32Font(base.Font):
dpi = 96
logpixelsy = dpi
logfont = LOGFONT()
logfont = LOGFONTW()
# Conversion of point size to device pixels
logfont.lfHeight = int(-size * logpixelsy // 72)
if bold:
@ -258,7 +258,7 @@ class Win32Font(base.Font):
else:
logfont.lfWeight = FW_NORMAL
logfont.lfItalic = italic
logfont.lfFaceName = asbytes(name)
logfont.lfFaceName = name
logfont.lfQuality = ANTIALIASED_QUALITY
user32.ReleaseDC(0, dc)
return logfont
@ -351,30 +351,29 @@ class GDIPlusGlyphRenderer(Win32GlyphRenderer):
self._bitmap_height = height
def render(self, text):
ch = ctypes.create_unicode_buffer(text)
len_ch = len(text)
# Layout rectangle; not clipped against so not terribly important.
width = 10000
height = self._bitmap_height
rect = Rectf(0, self._bitmap_height
- self.font.ascent + self.font.descent,
rect = Rectf(0, self._bitmap_height
- self.font.ascent + self.font.descent,
width, height)
# Set up GenericTypographic with 1 character measure range
generic = ctypes.c_void_p()
gdiplus.GdipStringFormatGetGenericTypographic(ctypes.byref(generic))
format = ctypes.c_void_p()
gdiplus.GdipCloneStringFormat(generic, ctypes.byref(format))
fmt = ctypes.c_void_p()
gdiplus.GdipCloneStringFormat(generic, ctypes.byref(fmt))
gdiplus.GdipDeleteStringFormat(generic)
# Measure advance
# --- Measure advance
# XXX HACK HACK HACK
# Windows GDI+ is a filthy broken toy. No way to measure the bounding
# box of a string, or to obtain LSB. What a joke.
#
#
# For historical note, GDI cannot be used because it cannot composite
# into a bitmap with alpha.
#
@ -382,7 +381,7 @@ class GDIPlusGlyphRenderer(Win32GlyphRenderer):
# supporting accurate text measurement with alpha composition in .NET
# 2.0 (WinForms) via the TextRenderer class; this has no C interface
# though, so we're entirely screwed.
#
#
# So anyway, we first try to get the width with GdipMeasureString.
# Then if it's a TrueType font, we use GetCharABCWidthsW to get the
# correct LSB. If it's a negative LSB, we move the layoutRect `rect`
@ -391,65 +390,77 @@ class GDIPlusGlyphRenderer(Win32GlyphRenderer):
# space and we don't pass the LSB info to the Glyph.set_bearings
bbox = Rectf()
flags = (StringFormatFlagsMeasureTrailingSpaces |
StringFormatFlagsNoClip |
flags = (StringFormatFlagsMeasureTrailingSpaces |
StringFormatFlagsNoClip |
StringFormatFlagsNoFitBlackBox)
gdiplus.GdipSetStringFormatFlags(format, flags)
gdiplus.GdipMeasureString(self._graphics,
ch,
gdiplus.GdipSetStringFormatFlags(fmt, flags)
gdiplus.GdipMeasureString(self._graphics,
ch,
len_ch,
self.font._gdipfont,
ctypes.byref(rect),
format,
self.font._gdipfont,
ctypes.byref(rect),
fmt,
ctypes.byref(bbox),
None,
None,
None)
lsb = 0
advance = int(math.ceil(bbox.width))
width = advance
# This hack bumps up the width if the font is italic;
# this compensates for some common fonts. It's also a stupid
# waste of texture memory.
if self.font.italic:
width += width // 2
# Do not enlarge more than the _rect width.
width = min(width, self._rect.Width)
# We only care about the advance from this whole thing.
advance = int(math.ceil(bbox.width))
# GDI functions only work for a single character so we transform
# grapheme \r\n into \r
if text == '\r\n':
text = '\r'
abc = ABC()
# Check if ttf font.
if gdi32.GetCharABCWidthsW(self._dc,
ord(text), ord(text), byref(abc)):
lsb = abc.abcA
width = abc.abcB
if lsb < 0:
# Negative LSB: we shift the layout rect to the right
# Otherwise we will cut the left part of the glyph
rect.x = -lsb
width -= lsb
else:
width += lsb
# XXX END HACK HACK HACK
abc = ABC()
width = 0
lsb = 0
ttf_font = True
# Use GDI to get code points for the text passed. This is almost always 1.
# For special unicode characters it may be comprised of 2+ codepoints. Get the width/lsb of each.
# Function only works on TTF fonts.
for codepoint in [ord(c) for c in text]:
if gdi32.GetCharABCWidthsW(self._dc, codepoint, codepoint, byref(abc)):
lsb += abc.abcA
width += abc.abcB
if lsb < 0:
# Negative LSB: we shift the layout rect to the right
# Otherwise we will cut the left part of the glyph
rect.x = -lsb
width -= lsb
else:
width += lsb
else:
ttf_font = False
break
# Almost always a TTF font. Haven't seen a modern font that GetCharABCWidthsW fails on.
# For safety, just use the advance as the width.
if not ttf_font:
width = advance
# This hack bumps up the width if the font is italic;
# this compensates for some common fonts. It's also a stupid
# waste of texture memory.
if self.font.italic:
width += width // 2
# Do not enlarge more than the _rect width.
width = min(width, self._rect.Width)
# Draw character to bitmap
gdiplus.GdipGraphicsClear(self._graphics, 0x00000000)
gdiplus.GdipDrawString(self._graphics,
ch,
len_ch,
self.font._gdipfont,
ctypes.byref(rect),
format,
fmt,
self._brush)
gdiplus.GdipFlush(self._graphics, 1)
gdiplus.GdipDeleteStringFormat(format)
gdiplus.GdipDeleteStringFormat(fmt)
bitmap_data = BitmapData()
gdiplus.GdipBitmapLockBits(self._bitmap,

View File

@ -153,6 +153,7 @@ but the same code using GDI+ rendered 16,600 glyphs per second.
import ctypes
from ctypes import wintypes
from pyglet.libs.win32 import LOGFONT, LOGFONTW
user32 = ctypes.windll.user32
gdi32 = ctypes.windll.gdi32
@ -209,62 +210,6 @@ FF_SCRIPT = 4 # handwritten
FF_DECORATIVE = 5 # novelty
class LOGFONT(ctypes.Structure):
# EnumFontFamiliesEx examines only 3 fields:
# - lfCharSet
# - lfFaceName - empty string enumerates one font in each available
# typeface name, valid typeface name gets all fonts
# with that name
# - lfPitchAndFamily - must be set to 0 [ ]
_fields_ = [
('lfHeight', wintypes.LONG),
# value > 0 specifies the largest size of *char cell* to match
# char cell = char height + internal leading
# value = 0 makes matched use default height for search
# value < 0 specifies the largest size of *char height* to match
('lfWidth', wintypes.LONG),
# average width also in *logical units*, which are pixels in
# default _mapping mode_ (MM_TEXT) for device
('lfEscapement', wintypes.LONG),
# string baseline rotation in tenths of degrees
('lfOrientation', wintypes.LONG),
# character rotation in tenths of degrees
('lfWeight', wintypes.LONG),
# 0 through 1000 400 is normal, 700 is bold, 0 is default
('lfItalic', BYTE),
('lfUnderline', BYTE),
('lfStrikeOut', BYTE),
('lfCharSet', BYTE),
# ANSI_CHARSET, BALTIC_CHARSET, ... - see *_CHARSET constants above
('lfOutPrecision', BYTE),
# many constants how the output must match height, width, pitch etc.
# OUT_DEFAULT_PRECIS
# [ ] TODO
('lfClipPrecision', BYTE),
# how to clip characters, no useful properties, leave default value
# CLIP_DEFAULT_PRECIS
('lfQuality', BYTE),
# ANTIALIASED_QUALITY
# CLEARTYPE_QUALITY
# DEFAULT_QUALITY
# DRAFT_QUALITY
# NONANTIALIASED_QUALITY
# PROOF_QUALITY
('lfPitchAndFamily', BYTE),
# DEFAULT_PITCH
# FIXED_PITCH - authoritative for monospace
# VARIABLE_PITCH
# stacked with any of
# FF_DECORATIVE - novelty
# FF_DONTCARE - default font
# FF_MODERN - stroke width ('pen width') near constant
# FF_ROMAN - proportional (variable char width) with serifs
# FF_SCRIPT - handwritten
# FF_SWISS - proportional without serifs
('lfFaceName', TCHAR * 32)]
# typeface name of the font - null-terminated string
class FONTSIGNATURE(ctypes.Structure):
# supported code pages and Unicode subranges for the font
# needed for NEWTEXTMETRICEX structure
@ -302,6 +247,32 @@ class NEWTEXTMETRIC(ctypes.Structure):
('ntmCellHeight', wintypes.UINT),
('ntmAvgWidth', wintypes.UINT)]
class NEWTEXTMETRICW(ctypes.Structure):
_fields_ = [
('tmHeight', wintypes.LONG),
('tmAscent', wintypes.LONG),
('tmDescent', wintypes.LONG),
('tmInternalLeading', wintypes.LONG),
('tmExternalLeading', wintypes.LONG),
('tmAveCharWidth', wintypes.LONG),
('tmMaxCharWidth', wintypes.LONG),
('tmWeight', wintypes.LONG),
('tmOverhang', wintypes.LONG),
('tmDigitizedAspectX', wintypes.LONG),
('tmDigitizedAspectY', wintypes.LONG),
('mFirstChar', wintypes.WCHAR),
('mLastChar', wintypes.WCHAR),
('mDefaultChar', wintypes.WCHAR),
('mBreakChar', wintypes.WCHAR),
('tmItalic', BYTE),
('tmUnderlined', BYTE),
('tmStruckOut', BYTE),
('tmPitchAndFamily', BYTE),
('tmCharSet', BYTE),
('tmFlags', wintypes.DWORD),
('ntmSizeEM', wintypes.UINT),
('ntmCellHeight', wintypes.UINT),
('ntmAvgWidth', wintypes.UINT)]
class NEWTEXTMETRICEX(ctypes.Structure):
# physical font attributes for True Type fonts
@ -310,6 +281,10 @@ class NEWTEXTMETRICEX(ctypes.Structure):
('ntmTm', NEWTEXTMETRIC),
('ntmFontSig', FONTSIGNATURE)]
class NEWTEXTMETRICEXW(ctypes.Structure):
_fields_ = [
('ntmTm', NEWTEXTMETRICW),
('ntmFontSig', FONTSIGNATURE)]
# type for a function that is called by the system for
# each font during execution of EnumFontFamiliesEx
@ -324,6 +299,15 @@ FONTENUMPROC = ctypes.WINFUNCTYPE(
wintypes.LPARAM
)
FONTENUMPROCW = ctypes.WINFUNCTYPE(
ctypes.c_int, # return non-0 to continue enumeration, 0 to stop
ctypes.POINTER(LOGFONTW),
ctypes.POINTER(NEWTEXTMETRICEXW),
wintypes.DWORD,
wintypes.LPARAM
)
# When running 64 bit windows, some types are not 32 bit, so Python/ctypes guesses wrong
gdi32.EnumFontFamiliesExA.argtypes = [
wintypes.HDC,
@ -333,6 +317,13 @@ gdi32.EnumFontFamiliesExA.argtypes = [
wintypes.DWORD]
gdi32.EnumFontFamiliesExW.argtypes = [
wintypes.HDC,
ctypes.POINTER(LOGFONTW),
FONTENUMPROCW,
wintypes.LPARAM,
wintypes.DWORD]
def _enum_font_names(logfont, textmetricex, fonttype, param):
"""callback function to be executed during EnumFontFamiliesEx
call for each font name. it stores names in global variable
@ -340,7 +331,7 @@ def _enum_font_names(logfont, textmetricex, fonttype, param):
global FONTDB
lf = logfont.contents
name = lf.lfFaceName.decode('utf-8')
name = lf.lfFaceName
# detect font type (vector|raster) and format (ttf)
# [ ] use Windows constant TRUETYPE_FONTTYPE
@ -406,7 +397,7 @@ def _enum_font_names(logfont, textmetricex, fonttype, param):
return 1 # non-0 to continue enumeration
enum_font_names = FONTENUMPROC(_enum_font_names)
enum_font_names = FONTENUMPROCW(_enum_font_names)
# --- /define
@ -445,9 +436,9 @@ def query(charset=DEFAULT_CHARSET):
# - enumerate all available charsets for a single font
# - other params?
logfont = LOGFONT(0, 0, 0, 0, 0, 0, 0, 0, charset, 0, 0, 0, 0, b'\0')
logfont = LOGFONTW(0, 0, 0, 0, 0, 0, 0, 0, charset, 0, 0, 0, 0, '')
FONTDB = [] # clear cached FONTDB for enum_font_names callback
res = gdi32.EnumFontFamiliesExA(
res = gdi32.EnumFontFamiliesExW(
hdc, # handle to device context
ctypes.byref(logfont),
enum_font_names, # pointer to callback function

View File

@ -55,15 +55,23 @@ context::
# ...
"""
from ctypes import c_char_p, cast, c_int
import warnings
from pyglet.gl.gl import (GL_EXTENSIONS, GL_RENDERER, GL_VENDOR,
GL_VERSION, GL_MAJOR_VERSION, GL_MINOR_VERSION)
from ctypes import c_char_p, cast
from pyglet.gl.gl import GL_EXTENSIONS, GL_RENDERER, GL_VENDOR, GL_VERSION
from pyglet.gl.gl import GL_MAJOR_VERSION, GL_MINOR_VERSION, GLint
from pyglet.gl.lib import GLException
from pyglet.util import asstr
def _get_number(parameter):
from pyglet.gl.gl import glGetIntegerv
number = GLint()
glGetIntegerv(parameter, number)
return number.value
class GLInfo:
"""Information interface for a single GL context.
@ -74,10 +82,13 @@ class GLInfo:
If you are using more than one context, you must call `set_active_context`
when the context is active for this `GLInfo` instance.
"""
have_context = False
version = '0.0'
_have_context = False
vendor = ''
renderer = ''
version = '0.0'
major_version = 0
minor_version = 0
opengl_api = 'gl'
extensions = set()
_have_info = False
@ -87,33 +98,35 @@ class GLInfo:
This method is called automatically for the default context.
"""
from pyglet.gl.gl import GLint, glGetIntegerv, glGetString, glGetStringi, GL_NUM_EXTENSIONS
from pyglet.gl.gl import glGetString, glGetStringi, GL_NUM_EXTENSIONS
self.have_context = True
self._have_context = True
if not self._have_info:
self.vendor = asstr(cast(glGetString(GL_VENDOR), c_char_p).value)
self.renderer = asstr(cast(glGetString(GL_RENDERER), c_char_p).value)
major_version = c_int()
glGetIntegerv(GL_MAJOR_VERSION, major_version)
self.major_version = major_version.value
minor_version = c_int()
glGetIntegerv(GL_MINOR_VERSION, minor_version)
self.minor_version = minor_version.value
self.version = asstr(cast(glGetString(GL_VERSION), c_char_p).value)
# NOTE: The version string requirements for gles is a lot stricter
# so using this to rely on detecting the API is not too unreasonable
self.opengl_api = "gles" if "opengl es" in self.version.lower() else "gl"
num_extensions = GLint()
glGetIntegerv(GL_NUM_EXTENSIONS, num_extensions)
self.extensions = (asstr(cast(glGetStringi(GL_EXTENSIONS, i), c_char_p).value)
for i in range(num_extensions.value))
self.extensions = set(self.extensions)
try:
self.major_version = _get_number(GL_MAJOR_VERSION)
self.minor_version = _get_number(GL_MINOR_VERSION)
num_ext = _get_number(GL_NUM_EXTENSIONS)
self.extensions = (asstr(cast(glGetStringi(GL_EXTENSIONS, i), c_char_p).value) for i in range(num_ext))
self.extensions = set(self.extensions)
except GLException:
pass # GL3 is likely not available
self._have_info = True
def remove_active_context(self):
self.have_context = False
self._have_context = False
self._have_info = False
def have_context(self):
return self._have_context
def have_extension(self, extension):
"""Determine if an OpenGL extension is available.
@ -125,7 +138,7 @@ class GLInfo:
:return: True if the extension is provided by the driver.
:rtype: bool
"""
if not self.have_context:
if not self._have_context:
warnings.warn('No GL context created yet.')
return extension in self.extensions
@ -135,7 +148,7 @@ class GLInfo:
:return: a list of the available extensions.
:rtype: list of str
"""
if not self.have_context:
if not self._have_context:
warnings.warn('No GL context created yet.')
return self.extensions
@ -145,7 +158,7 @@ class GLInfo:
:return: The major and minor version as a tuple
:rtype: tuple
"""
if not self.have_context:
if not self._have_context:
warnings.warn('No GL context created yet.')
return self.major_version, self.minor_version
@ -155,7 +168,7 @@ class GLInfo:
:return: The OpenGL version string
:rtype: str
"""
if not self.have_context:
if not self._have_context:
warnings.warn('No GL context created yet.')
return self.version
@ -172,7 +185,7 @@ class GLInfo:
:return: True if the requested or a later version is supported.
"""
if not self.have_context:
if not self._have_context:
warnings.warn('No GL context created yet.')
if not self.major_version and not self.minor_version:
return False
@ -186,7 +199,7 @@ class GLInfo:
:rtype: str
"""
if not self.have_context:
if not self._have_context:
warnings.warn('No GL context created yet.')
return self.renderer
@ -195,7 +208,7 @@ class GLInfo:
:rtype: str
"""
if not self.have_context:
if not self._have_context:
warnings.warn('No GL context created yet.')
return self.vendor
@ -205,7 +218,7 @@ class GLInfo:
:rtype: str
"""
if not self.have_context:
if not self._have_context:
warnings.warn('No GL context created yet.')
return self.opengl_api
@ -214,9 +227,6 @@ class GLInfo:
# (or all contexts have the same GL driver, a common case).
_gl_info = GLInfo()
set_active_context = _gl_info.set_active_context
remove_active_context = _gl_info.remove_active_context
have_extension = _gl_info.have_extension
get_extensions = _gl_info.get_extensions
get_version = _gl_info.get_version
get_version_string = _gl_info.get_version_string
@ -224,10 +234,7 @@ have_version = _gl_info.have_version
get_renderer = _gl_info.get_renderer
get_vendor = _gl_info.get_vendor
get_opengl_api = _gl_info.get_opengl_api
def have_context():
"""Determine if a default OpenGL context has been set yet.
:rtype: bool
"""
return _gl_info.have_context
have_extension = _gl_info.have_extension
have_context = _gl_info.have_context
remove_active_context = _gl_info.remove_active_context
set_active_context = _gl_info.set_active_context

View File

@ -40,7 +40,7 @@ Do not modify generated portions of this file.
from ctypes import *
from pyglet.gl.lib import link_GLX as _link_function
from pyglet.gl.lib import c_ptrdiff_t, c_void
from pyglet.gl.lib import c_void
if not _link_function:
raise ImportError('libGL.so is not available.')

View File

@ -41,6 +41,7 @@ Do not modify this file.
from ctypes import *
from pyglet.gl.lib import link_GLX as _link_function
from pyglet.gl.lib import c_void
# BEGIN GENERATED CONTENT (do not edit below this line)

View File

@ -44,7 +44,8 @@ __all__ = ['link_GL', 'link_WGL']
_debug_trace = pyglet.options['debug_trace']
gl_lib = ctypes.windll.opengl32
# gl_lib = ctypes.windll.opengl32
gl_lib = pyglet.lib.load_library('opengl32')
wgl_lib = gl_lib
if _debug_trace:

View File

@ -1,4 +1,3 @@
from typing import Dict, List
from ctypes import *
from weakref import proxy
@ -181,6 +180,56 @@ class _Uniform:
return f"Uniform('{self.name}', location={self.location}, length={self.length}, count={self.count})"
class ShaderSource:
"""GLSL source container for making source parsing simpler.
We support locating out attributes and applying #defines values.
NOTE: We do assume the source is neat enough to be parsed
this way and don't contain several statements in one line.
"""
def __init__(self, source: str, source_type: gl.GLenum):
"""Create a shader source wrapper."""
self._lines = source.strip().splitlines()
self._type = source_type
if not self._lines:
raise ValueError("Shader source is empty")
self._version = self._find_glsl_version()
if pyglet.gl.current_context.get_info().get_opengl_api() == "gles":
self._lines[0] = "#version 310 es"
self._lines.insert(1, "precision mediump float;")
if self._type == gl.GL_GEOMETRY_SHADER:
self._lines.insert(1, "#extension GL_EXT_geometry_shader : require")
if self._type == gl.GL_COMPUTE_SHADER:
self._lines.insert(1, "precision mediump image2D;")
self._version = self._find_glsl_version()
def validate(self) -> str:
"""Return the validated shader source."""
return "\n".join(self._lines)
def _find_glsl_version(self) -> int:
if self._lines[0].strip().startswith("#version"):
try:
return int(self._lines[0].split()[1])
except Exception:
pass
source = "\n".join(f"{str(i+1).zfill(3)}: {line} " for i, line in enumerate(self._lines))
raise ShaderException(("Cannot find #version flag in shader source. "
"A #version statement is required on the first line.\n"
"------------------------------------\n"
f"{source}"))
class Shader:
"""OpenGL Shader object"""
@ -247,7 +296,8 @@ class Shader:
else:
return f"{self.type.capitalize()} Shader '{shader_id}' compiled successfully."
def _get_shader_source(self, shader_id):
@staticmethod
def _get_shader_source(shader_id):
"""Get the shader source from the shader object"""
source_length = c_int(0)
glGetShaderiv(shader_id, GL_SHADER_SOURCE_LENGTH, source_length)
@ -698,61 +748,3 @@ class UniformBufferObject:
def __repr__(self):
return "{0}(id={1})".format(self.block.name + 'Buffer', self.buffer.id)
class ShaderSource:
"""
GLSL source container for making source parsing simpler.
We support locating out attributes and applying #defines values.
NOTE: We do assume the source is neat enough to be parsed
this way and don't contain several statements in one line.
"""
def __init__(self, source: str, source_type: gl.GLenum):
"""Create a shader source wrapper."""
self._source = source.strip()
self._type = source_type
self._lines = self._source.split("\n") if source else []
if not self._lines:
raise ValueError("Shader source is empty")
self._version = self._find_glsl_version()
if pyglet.gl.current_context.get_info().get_opengl_api() == "gles":
self._lines[0] = "#version 310 es"
self._lines.insert(1, "precision mediump float;")
if self._type == gl.GL_GEOMETRY_SHADER:
self._lines.insert(1, "#extension GL_EXT_geometry_shader : require")
if self._type == gl.GL_COMPUTE_SHADER:
self._lines.insert(1, "precision mediump image2D;")
self._version = self._find_glsl_version()
def validate(self) -> str:
"""Return the validated shader source."""
return "\n".join(self._lines)
def _find_glsl_version(self) -> int:
if self._lines[0].strip().startswith("#version"):
try:
return int(self._lines[0].split()[1])
except Exception:
pass
source = "\n".join(
f"{str(i+1).zfill(3)}: {line} " for i, line in enumerate(self._lines)
)
raise ShaderException(
(
"Cannot find #version in shader source. "
"A #version statement is required in the first line.\n"
f"------------------------------------\n"
f"{source}"
)
)

View File

@ -1329,7 +1329,7 @@ class Texture(AbstractImage):
buf = (GLubyte * (self.width * self.height * self.images * len(fmt)))()
# TODO: Clean up this temporary hack
if gl.current_context.get_info().get_opengl_api() == "gles":
if pyglet.gl.current_context.get_info().get_opengl_api() == "gles":
fbo = c_uint()
glGenFramebuffers(1, fbo)
glBindFramebuffer(GL_FRAMEBUFFER, fbo.value)

View File

@ -33,7 +33,7 @@
# POSSIBILITY OF SUCH DAMAGE.
# ----------------------------------------------------------------------------
from pyglet.com import pIUnknown
from pyglet.libs.win32.com import pIUnknown
from pyglet.image import *
from pyglet.image.codecs import *
from pyglet.libs.win32.constants import *

View File

@ -399,11 +399,6 @@ class IWICImagingFactory(com.pIUnknown):
_factory = IWICImagingFactory()
try:
ole32.CoInitializeEx(None, COINIT_MULTITHREADED)
except OSError as err:
warnings.warn(str(err))
ole32.CoCreateInstance(CLSID_WICImagingFactory,
None,
CLSCTX_INPROC_SERVER,

View File

@ -94,6 +94,10 @@ def dump_pyglet():
def dump_window():
"""Dump display, window, screen and default config info."""
from pyglet.gl import gl_info
if not gl_info.have_version(3):
print(f"Insufficient OpenGL version: {gl_info.get_version_string()}")
return
import pyglet.window
display = pyglet.canvas.get_display()
print('display:', repr(display))

View File

@ -45,6 +45,8 @@ from ctypes import c_int32 as _s32
from ctypes import c_int64 as _s64
from concurrent.futures import ThreadPoolExecutor
from typing import List
import pyglet
from pyglet.app.xlib import XlibSelectDevice
@ -580,7 +582,7 @@ class EvdevControllerManager(ControllerManager, XlibSelectDevice):
if controller:
self.dispatch_event('on_disconnect', controller)
def get_controllers(self) -> list[Controller]:
def get_controllers(self) -> List[Controller]:
return list(self._controllers.values())

View File

@ -188,8 +188,6 @@ class WintabTabletCanvas(TabletCanvas):
self.dispatch_event('on_motion', self._current_cursor, x, y, pressure, 0., 0.)
print(packet.pkButtons)
@pyglet.window.win32.Win32EventHandler(0)
def _event_wt_proximity(self, msg, wParam, lParam):
if wParam != self._context:

View File

@ -4,7 +4,7 @@ import threading
import pyglet
from pyglet import com
from pyglet.libs.win32 import com
from pyglet.event import EventDispatcher
from pyglet.libs.win32.types import *
from pyglet.libs.win32 import _ole32 as ole32, _oleaut32 as oleaut32
@ -281,8 +281,6 @@ def get_xinput_guids():
XInput device. Returns a list of strings containing pid/vid.
Monstrosity found at: https://docs.microsoft.com/en-us/windows/win32/xinput/xinput-and-directinput
"""
ole32.CoInitialize(None)
guids_found = []
locator = IWbemLocator()
@ -340,7 +338,6 @@ def get_xinput_guids():
guids_found.append(sdl_guid)
oleaut32.VariantClear(var)
ole32.CoUninitialize()
return guids_found

View File

@ -128,7 +128,7 @@ class LibraryLoader:
if not names:
raise ImportError("No library name specified")
platform_names = kwargs.get(self.platform, [])
if isinstance(platform_names, str):
platform_names = [platform_names]
@ -145,7 +145,7 @@ class LibraryLoader:
try:
lib = ctypes.cdll.LoadLibrary(name)
if _debug_lib:
print(name)
print(name, self.find_library(name))
if _debug_trace:
lib = _TraceLibrary(lib)
return lib
@ -225,10 +225,10 @@ class MachOLibraryLoader(LibraryLoader):
search_path.append(os.path.join(os.environ['CONDA_PREFIX'], 'lib', libname))
# pyinstaller.py sets sys.frozen to True, and puts dylibs in
# Contents/MacOS, which path pyinstaller puts in sys._MEIPASS
if (hasattr(sys, 'frozen') and hasattr(sys, '_MEIPASS') and
sys.frozen is True and pyglet.compat_platform == 'darwin'):
search_path.append(os.path.join(sys._MEIPASS, libname))
# Contents/macOS, which path pyinstaller puts in sys._MEIPASS
if getattr(sys, 'frozen', False) and getattr(sys, '_MEIPASS', None):
meipass = getattr(sys, '_MEIPASS')
search_path.append(os.path.join(meipass, libname))
# conda support
if os.environ.get('CONDA_PREFIX', False):
@ -316,7 +316,7 @@ class LinuxLibraryLoader(LibraryLoader):
try:
with open('/etc/ld.so.conf') as fid:
directories.extend([dir.strip() for dir in fid])
directories.extend([directory.strip() for directory in fid])
except IOError:
pass
@ -354,4 +354,5 @@ elif pyglet.compat_platform.startswith('linux'):
loader = LinuxLibraryLoader()
else:
loader = LibraryLoader()
load_library = loader.load_library

View File

@ -33,12 +33,13 @@
# POSSIBILITY OF SUCH DAMAGE.
# ----------------------------------------------------------------------------
import atexit
import struct
import pyglet
from . import com
from . import constants
from .types import *
from pyglet import com
IS64 = struct.calcsize("P") == 8
@ -46,12 +47,14 @@ _debug_win32 = pyglet.options['debug_win32']
if _debug_win32:
import traceback
_GetLastError = windll.kernel32.GetLastError
_SetLastError = windll.kernel32.SetLastError
_FormatMessageA = windll.kernel32.FormatMessageA
_log_win32 = open('debug_win32.log', 'w')
def format_error(err):
msg = create_string_buffer(256)
_FormatMessageA(constants.FORMAT_MESSAGE_FROM_SYSTEM,
@ -62,7 +65,8 @@ if _debug_win32:
len(msg),
c_void_p())
return msg.value
class DebugLibrary:
def __init__(self, lib):
self.lib = lib
@ -84,7 +88,6 @@ if _debug_win32:
else:
DebugLibrary = lambda lib: lib
_gdi32 = DebugLibrary(windll.gdi32)
_kernel32 = DebugLibrary(windll.kernel32)
_user32 = DebugLibrary(windll.user32)
@ -108,6 +111,8 @@ _gdi32.CreateDIBSection.restype = HBITMAP
_gdi32.CreateDIBSection.argtypes = [HDC, c_void_p, UINT, c_void_p, HANDLE, DWORD] # POINTER(BITMAPINFO)
_gdi32.CreateFontIndirectA.restype = HFONT
_gdi32.CreateFontIndirectA.argtypes = [POINTER(LOGFONT)]
_gdi32.CreateFontIndirectW.restype = HFONT
_gdi32.CreateFontIndirectW.argtypes = [POINTER(LOGFONTW)]
_gdi32.DeleteDC.restype = BOOL
_gdi32.DeleteDC.argtypes = [HDC]
_gdi32.DeleteObject.restype = BOOL
@ -122,7 +127,7 @@ _gdi32.GetCharABCWidthsW.restype = BOOL
_gdi32.GetCharABCWidthsW.argtypes = [HDC, UINT, UINT, POINTER(ABC)]
_gdi32.GetCharWidth32W.restype = BOOL
_gdi32.GetCharWidth32W.argtypes = [HDC, UINT, UINT, POINTER(INT)]
_gdi32.GetStockObject.restype = HGDIOBJ
_gdi32.GetStockObject.restype = HGDIOBJ
_gdi32.GetStockObject.argtypes = [c_int]
_gdi32.GetTextMetricsA.restype = BOOL
_gdi32.GetTextMetricsA.argtypes = [HDC, POINTER(TEXTMETRIC)]
@ -173,7 +178,8 @@ _user32.ClipCursor.argtypes = [LPRECT]
_user32.CreateIconIndirect.restype = HICON
_user32.CreateIconIndirect.argtypes = [POINTER(ICONINFO)]
_user32.CreateWindowExW.restype = HWND
_user32.CreateWindowExW.argtypes = [DWORD, c_wchar_p, c_wchar_p, DWORD, c_int, c_int, c_int, c_int, HWND, HMENU, HINSTANCE, LPVOID]
_user32.CreateWindowExW.argtypes = [DWORD, c_wchar_p, c_wchar_p, DWORD, c_int, c_int, c_int, c_int, HWND, HMENU,
HINSTANCE, LPVOID]
_user32.DefWindowProcW.restype = LRESULT
_user32.DefWindowProcW.argtypes = [HWND, UINT, WPARAM, LPARAM]
_user32.DestroyWindow.restype = BOOL
@ -191,8 +197,8 @@ _user32.GetClientRect.argtypes = [HWND, LPRECT]
_user32.GetCursorPos.restype = BOOL
_user32.GetCursorPos.argtypes = [LPPOINT]
# workaround for win 64-bit, see issue #664
_user32.GetDC.restype = c_void_p # HDC
_user32.GetDC.argtypes = [c_void_p] # [HWND]
_user32.GetDC.restype = c_void_p # HDC
_user32.GetDC.argtypes = [c_void_p] # [HWND]
_user32.GetDesktopWindow.restype = HWND
_user32.GetDesktopWindow.argtypes = []
_user32.GetKeyState.restype = c_short
@ -226,8 +232,8 @@ _user32.RegisterHotKey.argtypes = [HWND, c_int, UINT, UINT]
_user32.ReleaseCapture.restype = BOOL
_user32.ReleaseCapture.argtypes = []
# workaround for win 64-bit, see issue #664
_user32.ReleaseDC.restype = c_int32 # c_int
_user32.ReleaseDC.argtypes = [c_void_p, c_void_p] # [HWND, HDC]
_user32.ReleaseDC.restype = c_int32 # c_int
_user32.ReleaseDC.argtypes = [c_void_p, c_void_p] # [HWND, HDC]
_user32.ScreenToClient.restype = BOOL
_user32.ScreenToClient.argtypes = [HWND, LPPOINT]
_user32.SetCapture.restype = HWND
@ -275,13 +281,13 @@ _user32.GetRawInputData.argtypes = [HRAWINPUT, UINT, LPVOID, PUINT, UINT]
_user32.ChangeWindowMessageFilterEx.restype = BOOL
_user32.ChangeWindowMessageFilterEx.argtypes = [HWND, UINT, DWORD, c_void_p]
#dwmapi
# dwmapi
_dwmapi.DwmIsCompositionEnabled.restype = c_int
_dwmapi.DwmIsCompositionEnabled.argtypes = [POINTER(INT)]
_dwmapi.DwmFlush.restype = c_int
_dwmapi.DwmFlush.argtypes = []
#_shell32
# _shell32
_shell32.DragAcceptFiles.restype = c_void
_shell32.DragAcceptFiles.argtypes = [HWND, BOOL]
_shell32.DragFinish.restype = c_void
@ -306,9 +312,23 @@ _ole32.CoCreateInstance.argtypes = [com.REFIID, c_void_p, DWORD, com.REFIID, c_v
_ole32.CoSetProxyBlanket.restype = HRESULT
_ole32.CoSetProxyBlanket.argtypes = (c_void_p, DWORD, DWORD, c_void_p, DWORD, DWORD, c_void_p, DWORD)
# oleaut32
_oleaut32.VariantInit.restype = c_void_p
_oleaut32.VariantInit.argtypes = [c_void_p]
_oleaut32.VariantClear.restype = HRESULT
_oleaut32.VariantClear.argtypes = [c_void_p]
# Initialize COM in MTA mode. Required for: WIC (DirectWrite), WMF, and XInput
try:
_ole32.CoInitializeEx(None, constants.COINIT_MULTITHREADED)
except OSError as err:
warnings.warn("Could not set COM MTA mode. Unexpected behavior may occur.")
def _uninitialize():
try:
_ole32.CoUninitialize()
except OSError:
pass
atexit.register(_uninitialize)

View File

@ -77,7 +77,7 @@ from pyglet.util import debug_print
_debug_com = debug_print('debug_com')
if sys.platform != 'win32':
raise ImportError('pyglet.com requires a Windows build of Python')
raise ImportError('pyglet.libs.win32.com requires a Windows build of Python')
class GUID(ctypes.Structure):

View File

@ -34,7 +34,7 @@
# ----------------------------------------------------------------------------
import ctypes
from pyglet import com
from pyglet.libs.win32 import com
lib = ctypes.oledll.dinput8

View File

@ -35,7 +35,7 @@
import sys
import ctypes
from pyglet import com
from . import com
from ctypes import *
from ctypes.wintypes import *
@ -269,6 +269,25 @@ class LOGFONT(Structure):
]
class LOGFONTW(Structure):
_fields_ = [
('lfHeight', LONG),
('lfWidth', LONG),
('lfEscapement', LONG),
('lfOrientation', LONG),
('lfWeight', LONG),
('lfItalic', BYTE),
('lfUnderline', BYTE),
('lfStrikeOut', BYTE),
('lfCharSet', BYTE),
('lfOutPrecision', BYTE),
('lfClipPrecision', BYTE),
('lfQuality', BYTE),
('lfPitchAndFamily', BYTE),
('lfFaceName', (WCHAR * LF_FACESIZE))
]
class TRACKMOUSEEVENT(Structure):
_fields_ = [
('cbSize', DWORD),

View File

@ -35,15 +35,15 @@
"""Matrix and Vector math.
This module provides Vector and Matrix objects, include Vec2, Vec3, Vec4,
Mat3, and Mat4. Most common operations are supported, and many helper
methods are included for rotating, scaling, and transforming. The
:py:class:`~pyglet.matrix.Mat4` includes class methods for creating
orthographic and perspective projection matrixes.
This module provides Vector and Matrix objects, including Vec2, Vec3,
Vec4, Mat3, and Mat4. Most common matrix and vector operations are
supported. Helper methods are included for rotating, scaling, and
transforming. The :py:class:`~pyglet.matrix.Mat4` includes class methods
for creating orthographic and perspective projection matrixes.
:note: For performance, these objects' subclass the `tuple` type. They
are therefore immutable - all operations return a new object; the
object is not updated in-place.
:note: For performance, Matrixes subclass the `tuple` type. They
are therefore immutable - all operations return a new object;
the object is not updated in-place.
"""
import math as _math
@ -56,7 +56,10 @@ def clamp(num, min_val, max_val):
return max(min(num, max_val), min_val)
class Vec2(tuple):
class Vec2:
__slots__ = 'x', 'y'
"""A two-dimensional vector represented as an X Y coordinate pair.
:parameters:
@ -64,83 +67,40 @@ class Vec2(tuple):
The X coordinate of the vector.
`y` : int or float :
The Y coordinate of the vector.
Vectors must be created with either 0 or 2 values. If no
arguments are provided a vector with the coordinates 0, 0 is created.
Vectors are stored as a tuple and therefore immutable and cannot be modified directly
"""
def __new__(cls, *args):
assert len(args) in (0, 2), "0 or 2 values are required for Vec2 types."
return super().__new__(Vec2, args or (0, 0))
def __init__(self, x=0.0, y=0.0):
self.x = x
self.y = y
@staticmethod
def from_polar(mag, angle):
"""Create a new vector from the given polar coodinates.
def __iter__(self):
yield self.x
yield self.y
:parameters:
`mag` : int or float :
The magnitude of the vector.
`angle` : int or float :
The angle of the vector in radians.
def __len__(self):
return 2
:returns: A new vector with the given angle and magnitude.
:rtype: Vec2
"""
return Vec2(mag * _math.cos(angle), mag * _math.sin(angle))
@property
def x(self):
"""The X coordinate of the vector.
:type: float
"""
return self[0]
@property
def y(self):
"""The Y coordinate of the vector.
:type: float
"""
return self[1]
@property
def heading(self):
"""The angle of the vector in radians.
:type: float
"""
return _math.atan2(self[1], self[0])
@property
def mag(self):
"""The magnitude, or length of the vector. The distance between the coordinates and the origin.
Alias of abs(self).
:type: float
"""
return self.__abs__()
def __getitem__(self, item):
return (self.x, self.y)[item]
def __add__(self, other):
return Vec2(self[0] + other[0], self[1] + other[1])
return Vec2(self.x + other.x, self.y + other.y)
def __sub__(self, other):
return Vec2(self[0] - other[0], self[1] - other[1])
return Vec2(self.x - other.x, self.y - other.y)
def __mul__(self, other):
return Vec2(self[0] * other[0], self[1] * other[1])
return Vec2(self.x * other.x, self.y * other.y)
def __truediv__(self, other):
return Vec2(self[0] / other[0], self[1] / other[1])
return Vec2(self.x / other.x, self.y / other.y)
def __abs__(self):
return _math.sqrt(self[0] ** 2 + self[1] ** 2)
return _math.sqrt(self.x ** 2 + self.y ** 2)
def __neg__(self):
return Vec2(-self[0], -self[1])
return Vec2(-self.x, -self.y)
def __round__(self, ndigits=None):
return Vec2(*(round(v, ndigits) for v in self))
@ -153,6 +113,21 @@ class Vec2(tuple):
else:
return self.__add__(other)
@staticmethod
def from_polar(mag, angle):
"""Create a new vector from the given polar coodinates.
:parameters:
`mag` : int or float :
The magnitude of the vector.
`angle` : int or float :
The angle of the vector in radians.
:returns: A new vector with the given angle and magnitude.
:rtype: Vec2
"""
return Vec2(mag * _math.cos(angle), mag * _math.sin(angle))
def from_magnitude(self, magnitude):
"""Create a new Vector of the given magnitude by normalizing,
then scaling the vector. The heading remains unchanged.
@ -179,6 +154,24 @@ class Vec2(tuple):
mag = self.__abs__()
return Vec2(mag * _math.cos(heading), mag * _math.sin(heading))
@property
def heading(self):
"""The angle of the vector in radians.
:type: float
"""
return _math.atan2(self.y, self.x)
@property
def mag(self):
"""The magnitude, or length of the vector. The distance between the coordinates and the origin.
Alias of abs(self).
:type: float
"""
return self.__abs__()
def limit(self, maximum):
"""Limit the magnitude of the vector to the value used for the max parameter.
@ -189,7 +182,7 @@ class Vec2(tuple):
:returns: Either self or a new vector with the maximum magnitude.
:rtype: Vec2
"""
if self[0] ** 2 + self[1] ** 2 > maximum * maximum:
if self.x ** 2 + self.y ** 2 > maximum * maximum:
return self.from_magnitude(maximum)
return self
@ -207,8 +200,8 @@ class Vec2(tuple):
:returns: A new interpolated vector.
:rtype: Vec2
"""
return Vec2(self[0] + (alpha * (other[0] - self[0])),
self[1] + (alpha * (other[1] - self[1])))
return Vec2(self.x + (alpha * (other.x - self.x)),
self.y + (alpha * (other.y - self.y)))
def scale(self, value):
"""Multiply the vector by a scalar value.
@ -220,7 +213,7 @@ class Vec2(tuple):
:returns: A new vector scaled by the value.
:rtype: Vec2
"""
return Vec2(self[0] * value, self[1] * value)
return Vec2(self.x * value, self.y * value)
def rotate(self, angle):
"""Create a new Vector rotated by the angle. The magnitude remains unchanged.
@ -246,7 +239,7 @@ class Vec2(tuple):
:returns: The distance between the two vectors.
:rtype: float
"""
return _math.sqrt(((other[0] - self[0]) ** 2) + ((other[1] - self[1]) ** 2))
return _math.sqrt(((other.x - self.x) ** 2) + ((other.y - self.y) ** 2))
def normalize(self):
"""Normalize the vector to have a magnitude of 1. i.e. make it a unit vector.
@ -256,7 +249,7 @@ class Vec2(tuple):
"""
d = self.__abs__()
if d:
return Vec2(self[0] / d, self[1] / d)
return Vec2(self.x / d, self.y / d)
return self
def clamp(self, min_val, max_val):
@ -271,7 +264,7 @@ class Vec2(tuple):
:returns: A new vector with clamped X and Y components.
:rtype: Vec2
"""
return Vec2(clamp(self[0], min_val, max_val), clamp(self[1], min_val, max_val))
return Vec2(clamp(self.x, min_val, max_val), clamp(self.y, min_val, max_val))
def dot(self, other):
"""Calculate the dot product of this vector and another 2D vector.
@ -283,7 +276,7 @@ class Vec2(tuple):
:returns: The dot product of the two vectors.
:rtype: float
"""
return self[0] * other[0] + self[1] * other[1]
return self.x * other.x + self.y * other.y
def __getattr__(self, attrs):
try:
@ -294,10 +287,13 @@ class Vec2(tuple):
raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{attrs}'")
def __repr__(self):
return f"Vec2({self[0]}, {self[1]})"
return f"Vec2({self.x}, {self.y})"
class Vec3(tuple):
class Vec3:
__slots__ = 'x', 'y', 'z'
"""A three-dimensional vector represented as X Y Z coordinates.
:parameters:
@ -307,68 +303,52 @@ class Vec3(tuple):
The Y coordinate of the vector.
`z` : int or float :
The Z coordinate of the vector.
3D Vectors must be created with either 0 or 3 values. If no arguments
are provided, a vector with the coordinates 0, 0, 0 is created.
Vectors are stored as a tuple and therefore immutable and cannot be modified directly
"""
def __new__(cls, *args):
assert len(args) in (0, 3), "0 or 3 values are required for Vec3 types."
return super().__new__(Vec3, args or (0, 0, 0))
def __init__(self, x=0.0, y=0.0, z=0.0):
self.x = x
self.y = y
self.z = z
@property
def x(self):
"""The X coordinate of the vector.
def __iter__(self):
yield self.x
yield self.y
yield self.z
:type: float
"""
return self[0]
def __getitem__(self, item):
return (self.x, self.y, self.z)[item]
@property
def y(self):
"""The Y coordinate of the vector.
def __len__(self):
return 3
:type: float
"""
return self[1]
@property
def z(self):
"""The Z coordinate of the vector.
:type: float
"""
return self[2]
@property
def mag(self):
"""The magnitude, or length of the vector. The distance between the coordinates and the origin.
Alias of abs(self).
:type: float
:type: float
"""
return self.__abs__()
def __add__(self, other):
return Vec3(self[0] + other[0], self[1] + other[1], self[2] + other[2])
return Vec3(self.x + other.x, self.y + other.y, self.z + other.z)
def __sub__(self, other):
return Vec3(self[0] - other[0], self[1] - other[1], self[2] - other[2])
return Vec3(self.x - other.x, self.y - other.y, self.z - other.z)
def __mul__(self, other):
return Vec3(self[0] * other[0], self[1] * other[1], self[2] * other[2])
return Vec3(self.x * other.x, self.y * other.y, self.z * other.z)
def __truediv__(self, other):
return Vec3(self[0] / other[0], self[1] / other[1], self[2] / other[2])
return Vec3(self.x / other.x, self.y / other.y, self.z / other.z)
def __abs__(self):
return _math.sqrt(self[0] ** 2 + self[1] ** 2 + self[2] ** 2)
return _math.sqrt(self.x ** 2 + self.y ** 2 + self.z ** 2)
def __neg__(self):
return Vec3(-self[0], -self[1], -self[2])
return Vec3(-self.x, -self.y, -self.z)
def __round__(self, ndigits=None):
return Vec3(*(round(v, ndigits) for v in self))
@ -404,7 +384,7 @@ class Vec3(tuple):
:returns: Either self or a new vector with the maximum magnitude.
:rtype: Vec3
"""
if self[0] ** 2 + self[1] ** 2 + self[2] ** 2 > maximum * maximum * maximum:
if self.x ** 2 + self.y ** 2 + self.z ** 2 > maximum * maximum * maximum:
return self.from_magnitude(max)
return self
@ -418,21 +398,21 @@ class Vec3(tuple):
:returns: The cross product of the two vectors.
:rtype: float
"""
return Vec3((self[1] * other[2]) - (self[2] * other[1]),
(self[2] * other[0]) - (self[0] * other[2]),
(self[0] * other[1]) - (self[1] * other[0]))
return Vec3((self.y * other.z) - (self.z * other.y),
(self.z * other.x) - (self.x * other.z),
(self.x * other.y) - (self.y * other.x))
def dot(self, other):
"""Calculate the dot product of this vector and another 3D vector.
:parameters:
:parameters:
`other` : Vec3 :
The other vector.
:returns: The dot product of the two vectors.
:rtype: float
"""
return self[0] * other[0] + self[1] * other[1] + self[2] * other[2]
return self.x * other.x + self.y * other.y + self.z * other.z
def lerp(self, other, alpha):
"""Create a new vector lineraly interpolated between this vector and another vector.
@ -448,9 +428,9 @@ class Vec3(tuple):
:returns: A new interpolated vector.
:rtype: Vec3
"""
return Vec3(self[0] + (alpha * (other[0] - self[0])),
self[1] + (alpha * (other[1] - self[1])),
self[2] + (alpha * (other[2] - self[2])))
return Vec3(self.x + (alpha * (other.x - self.x)),
self.y + (alpha * (other.y - self.y)),
self.z + (alpha * (other.z - self.z)))
def scale(self, value):
"""Multiply the vector by a scalar value.
@ -462,7 +442,7 @@ class Vec3(tuple):
:returns: A new vector scaled by the value.
:rtype: Vec3
"""
return Vec3(self[0] * value, self[1] * value, self[2] * value)
return Vec3(self.x * value, self.y * value, self.z * value)
def distance(self, other):
"""Calculate the distance between this vector and another 3D vector.
@ -474,9 +454,9 @@ class Vec3(tuple):
:returns: The distance between the two vectors.
:rtype: float
"""
return _math.sqrt(((other[0] - self[0]) ** 2) +
((other[1] - self[1]) ** 2) +
((other[2] - self[2]) ** 2))
return _math.sqrt(((other.x - self.x) ** 2) +
((other.y - self.y) ** 2) +
((other.z - self.z) ** 2))
def normalize(self):
"""Normalize the vector to have a magnitude of 1. i.e. make it a unit vector.
@ -486,7 +466,7 @@ class Vec3(tuple):
"""
d = self.__abs__()
if d:
return Vec3(self[0] / d, self[1] / d, self[2] / d)
return Vec3(self.x / d, self.y / d, self.z / d)
return self
def clamp(self, min_val, max_val):
@ -501,9 +481,9 @@ class Vec3(tuple):
:returns: A new vector with clamped X, Y and Z components.
:rtype: Vec3
"""
return Vec3(clamp(self[0], min_val, max_val),
clamp(self[1], min_val, max_val),
clamp(self[2], min_val, max_val))
return Vec3(clamp(self.x, min_val, max_val),
clamp(self.y, min_val, max_val),
clamp(self.z, min_val, max_val))
def __getattr__(self, attrs):
try:
@ -514,48 +494,62 @@ class Vec3(tuple):
raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{attrs}'")
def __repr__(self):
return f"Vec3({self[0]}, {self[1]}, {self[2]})"
return f"Vec3({self.x}, {self.y}, {self.z})"
class Vec4(tuple):
class Vec4:
def __new__(cls, *args):
assert len(args) in (0, 4), "0 or 4 values are required for Vec4 types."
return super().__new__(Vec4, args or (0, 0, 0, 0))
__slots__ = 'x', 'y', 'z', 'w'
@property
def x(self):
return self[0]
"""A four-dimensional vector represented as X Y Z W coordinates.
@property
def y(self):
return self[1]
:parameters:
`x` : int or float :
The X coordinate of the vector.
`y` : int or float :
The Y coordinate of the vector.
`z` : int or float :
The Z coordinate of the vector.
`w` : int or float :
The W coordinate of the vector.
@property
def z(self):
return self[2]
"""
@property
def w(self):
return self[3]
def __init__(self, x=0.0, y=0.0, z=0.0, w=0.0):
self.x = x
self.y = y
self.z = z
self.w = w
def __iter__(self):
yield self.x
yield self.y
yield self.z
yield self.w
def __getitem__(self, item):
return (self.x, self.y, self.z, self.w)[item]
def __len__(self):
return 4
def __add__(self, other):
return Vec4(self[0] + other[0], self[1] + other[1], self[2] + other[2], self[3] + other[3])
return Vec4(self.x + other.x, self.y + other.y, self.z + other.z, self.w + other.w)
def __sub__(self, other):
return Vec4(self[0] - other[0], self[1] - other[1], self[2] - other[2], self[3] - other[3])
return Vec4(self.x - other.x, self.y - other.y, self.z - other.z, self.w - other.w)
def __mul__(self, other):
return Vec4(self[0] * other[0], self[1] * other[1], self[2] * other[2], self[3] * other[3])
return Vec4(self.x * other.x, self.y * other.y, self.z * other.z, self.w * other.w)
def __truediv__(self, other):
return Vec4(self[0] / other[0], self[1] / other[1], self[2] / other[2], self[3] / other[3])
return Vec4(self.x / other.x, self.y / other.y, self.z / other.z, self.w / other.w)
def __abs__(self):
return _math.sqrt(self[0] ** 2 + self[1] ** 2 + self[2] ** 2 + self[3] ** 2)
return _math.sqrt(self.x ** 2 + self.y ** 2 + self.z ** 2 + self.w ** 2)
def __neg__(self):
return Vec4(-self[0], -self[1], -self[2], -self[3])
return Vec4(-self.x, -self.y, -self.z, -self.w)
def __round__(self, ndigits=None):
return Vec4(*(round(v, ndigits) for v in self))
@ -567,34 +561,34 @@ class Vec4(tuple):
return self.__add__(other)
def lerp(self, other, alpha):
return Vec4(self[0] + (alpha * (other[0] - self[0])),
self[1] + (alpha * (other[1] - self[1])),
self[2] + (alpha * (other[2] - self[2])),
self[3] + (alpha * (other[3] - self[3])))
return Vec4(self.x + (alpha * (other.x - self.x)),
self.y + (alpha * (other.y - self.y)),
self.z + (alpha * (other.z - self.z)),
self.w + (alpha * (other.w - self.w)))
def scale(self, value):
return Vec4(self[0] * value, self[1] * value, self[2] * value, self[3] * value)
return Vec4(self.x * value, self.y * value, self.z * value, self.w * value)
def distance(self, other):
return _math.sqrt(((other[0] - self[0]) ** 2) +
((other[1] - self[1]) ** 2) +
((other[2] - self[2]) ** 2) +
((other[3] - self[3]) ** 2))
return _math.sqrt(((other.x - self.x) ** 2) +
((other.y - self.y) ** 2) +
((other.z - self.z) ** 2) +
((other.w - self.w) ** 2))
def normalize(self):
d = self.__abs__()
if d:
return Vec4(self[0] / d, self[1] / d, self[2] / d, self[3] / d)
return Vec4(self.x / d, self.y / d, self.z / d, self.w / d)
return self
def clamp(self, min_val, max_val):
return Vec4(clamp(self[0], min_val, max_val),
clamp(self[1], min_val, max_val),
clamp(self[2], min_val, max_val),
clamp(self[3], min_val, max_val))
return Vec4(clamp(self.x, min_val, max_val),
clamp(self.y, min_val, max_val),
clamp(self.z, min_val, max_val),
clamp(self.w, min_val, max_val))
def dot(self, other):
return self[0] * other[0] + self[1] * other[1] + self[2] * other[2] + self[3] * other[3]
return self.x * other.x + self.y * other.y + self.z * other.z + self.w * other.w
def __getattr__(self, attrs):
try:
@ -605,7 +599,7 @@ class Vec4(tuple):
raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{attrs}'")
def __repr__(self):
return f"Vec4({self[0]}, {self[1]}, {self[2]}, {self[3]})"
return f"Vec4({self.x}, {self.y}, {self.z}, {self.w})"
class Mat3(tuple):
@ -667,7 +661,7 @@ class Mat3(tuple):
def __mul__(self, other):
raise NotImplementedError("Please use the @ operator for Matrix multiplication.")
def __matmul__(self, other) -> 'Mat3':
def __matmul__(self, other) -> 'Mat3' or 'Vec3':
assert len(other) in (3, 9), "Can only multiply with Mat3 or Vec3 types"
if type(other) is Vec3:
@ -754,12 +748,16 @@ class Mat4(tuple):
tx, ty, tz, 1.0))
@classmethod
def perspective_projection(cls, left, right, bottom, top, z_near, z_far, fov=60) -> 'Mat4':
"""Create a Mat4 perspective projection matrix."""
width = right - left
height = top - bottom
aspect = width / height
def perspective_projection(cls, aspect, z_near, z_far, fov=60) -> 'Mat4':
"""
Create a Mat4 perspective projection matrix.
:Parameters:
`aspect` : The aspect ratio as a `float`
`z_near` : The near plane as a `float`
`z_far` : The far plane as a `float`
`fov` : Field of view in degrees as a `float`
"""
xy_max = z_near * _math.tan(fov * _math.pi / 360)
y_min = -xy_max
x_min = -xy_max
@ -781,11 +779,11 @@ class Mat4(tuple):
@classmethod
def from_translation(cls, vector: Vec3) -> 'Mat4':
"""Create a translaton matrix from a Vec3.
"""Create a translation matrix from a Vec3.
:Parameters:
`vector` : A `Vec3`, or 3 component tuple of float or int
Vec3 or tuple with x, y and z translaton values
Vec3 or tuple with x, y and z translation values
"""
return cls((1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
@ -800,7 +798,7 @@ class Mat4(tuple):
`angle` : A `float` :
The angle as a float.
`vector` : A `Vec3`, or 3 component tuple of float or int :
Vec3 or tuple with x, y and z translaton values
Vec3 or tuple with x, y and z translation values
"""
return cls().rotate(angle, vector)
@ -869,7 +867,7 @@ class Mat4(tuple):
return Mat4(self) @ Mat4((ra, rb, rc, 0, re, rf, rg, 0, ri, rj, rk, 0, 0, 0, 0, 1))
def transpose(self) -> 'Mat4':
"""Get a tranpose of this Matrix."""
"""Get a transpose of this Matrix."""
return Mat4(self[0::4] + self[1::4] + self[2::4] + self[3::4])
def __add__(self, other) -> 'Mat4':
@ -941,7 +939,7 @@ class Mat4(tuple):
def __mul__(self, other):
raise NotImplementedError("Please use the @ operator for Matrix multiplication.")
def __matmul__(self, other) -> 'Mat4':
def __matmul__(self, other) -> 'Mat4' or 'Vec4':
assert len(other) in (4, 16), "Can only multiply with Mat4 or Vec4 types"
if type(other) is Vec4:

View File

@ -36,14 +36,15 @@ import os
import platform
import warnings
from pyglet import com, image
from pyglet.util import debug_print, DecodeException
from pyglet import image
from pyglet.libs.win32 import _kernel32 as kernel32
from pyglet.libs.win32 import _ole32 as ole32
from pyglet.libs.win32 import com
from pyglet.libs.win32.constants import *
from pyglet.libs.win32.types import *
from pyglet.media import Source
from pyglet.media.codecs import AudioFormat, AudioData, VideoFormat, MediaDecoder, StaticSource
from pyglet.util import debug_print, DecodeException
_debug = debug_print('debug_media')
@ -827,16 +828,8 @@ class WMFSource(Source):
class WMFDecoder(MediaDecoder):
def __init__(self):
self.ole32 = None
self.MFShutdown = None
try:
# Coinitialize supposed to be called for COMs?
ole32.CoInitializeEx(None, COINIT_MULTITHREADED)
except OSError as err:
warnings.warn(str(err))
try:
MFStartup(MF_VERSION, 0)
except OSError as err:
@ -844,7 +837,6 @@ class WMFDecoder(MediaDecoder):
self.extensions = self._build_decoder_extensions()
self.ole32 = ole32
self.MFShutdown = MFShutdown
assert _debug('Windows Media Foundation: Initialized.')
@ -884,8 +876,6 @@ class WMFDecoder(MediaDecoder):
def __del__(self):
if self.MFShutdown is not None:
self.MFShutdown()
if self.ole32 is not None:
self.ole32.CoUninitialize()
def get_decoders():

View File

@ -1,4 +1,4 @@
from pyglet import com
from pyglet.libs.win32 import com
from pyglet.libs.win32 import _ole32 as ole32
from pyglet.libs.win32.constants import CLSCTX_INPROC_SERVER
from pyglet.libs.win32.types import *

View File

@ -92,7 +92,7 @@ class DirectSoundAudioPlayer(AbstractAudioPlayer):
# Up to one audio data may be buffered if too much data was received
# from the source that could not be written immediately into the
# buffer. See refill().
# buffer. See _refill().
self._audiodata_buffer = None
# Theoretical write and play cursors for an infinite buffer. play
@ -106,7 +106,7 @@ class DirectSoundAudioPlayer(AbstractAudioPlayer):
self._eos_cursor = None
# Indexes into DSound circular buffer. Complications ensue wrt each
# other to avoid writing over the play cursor. See get_write_size and
# other to avoid writing over the play cursor. See _get_write_size and
# write().
self._play_cursor_ring = 0
self._write_cursor_ring = 0
@ -126,7 +126,7 @@ class DirectSoundAudioPlayer(AbstractAudioPlayer):
self._ds_buffer.current_position = 0
self.refill(self._buffer_size)
self._refill(self._buffer_size)
def __del__(self):
# We decrease the IDirectSound refcount
@ -167,9 +167,16 @@ class DirectSoundAudioPlayer(AbstractAudioPlayer):
del self._events[:]
del self._timestamps[:]
def refill(self, write_size):
def refill_buffer(self):
write_size = self._get_write_size()
if write_size > self.min_buffer_size:
self._refill(write_size)
return True
return False
def _refill(self, write_size):
while write_size > 0:
assert _debug('refill, write_size =', write_size)
assert _debug('_refill, write_size =', write_size)
audio_data = self._get_audiodata()
if audio_data is not None:
@ -277,7 +284,7 @@ class DirectSoundAudioPlayer(AbstractAudioPlayer):
self.stop()
self._dispatch_new_event('on_eos')
def get_write_size(self):
def _get_write_size(self):
self.update_play_cursor()
play_cursor = self._play_cursor
@ -368,8 +375,8 @@ class DirectSoundAudioPlayer(AbstractAudioPlayer):
self._ds_buffer.cone_outside_volume = volume
def prefill_audio(self):
write_size = self.get_write_size()
self.refill(write_size)
write_size = self._get_write_size()
self._refill(write_size)
class DirectSoundDriver(AbstractAudioDriver):
@ -400,8 +407,9 @@ class DirectSoundDriver(AbstractAudioDriver):
return DirectSoundListener(self._ds_listener, self._ds_driver.primary_buffer)
def delete(self):
if hasattr(self, 'worker'):
self.worker.stop()
# Make sure the _ds_listener is deleted before the _ds_driver
self.worker.stop()
self._ds_listener = None

View File

@ -34,7 +34,7 @@
# ----------------------------------------------------------------------------
import ctypes
from pyglet import com
from pyglet.libs.win32 import com
lib = ctypes.oledll.dsound

View File

@ -158,10 +158,10 @@ class OpenALAudioPlayer(AbstractAudioPlayer):
# Up to one audio data may be buffered if too much data was received
# from the source that could not be written immediately into the
# buffer. See refill().
# buffer. See _refill().
self._audiodata_buffer = None
self.refill(self.ideal_buffer_size)
self._refill(self.ideal_buffer_size)
def __del__(self):
self.delete()
@ -258,7 +258,7 @@ class OpenALAudioPlayer(AbstractAudioPlayer):
_, event = self._events.pop(0)
event._sync_dispatch_to_player(self.player)
def get_write_size(self):
def _get_write_size(self):
self._update_play_cursor()
buffer_size = int(self._write_cursor - self._play_cursor)
@ -268,8 +268,15 @@ class OpenALAudioPlayer(AbstractAudioPlayer):
assert _debug("Write size {} bytes".format(write_size))
return write_size
def refill(self, write_size):
assert _debug('refill', write_size)
def refill_buffer(self):
write_size = self._get_write_size()
if write_size > self.min_buffer_size:
self._refill(write_size)
return True
return False
def _refill(self, write_size):
assert _debug('_refill', write_size)
while write_size > self.min_buffer_size:
audio_data = self._get_audiodata()
@ -391,5 +398,5 @@ class OpenALAudioPlayer(AbstractAudioPlayer):
self.alsource.cone_outer_gain = cone_outer_gain
def prefill_audio(self):
write_size = self.get_write_size()
self.refill(write_size)
write_size = self._get_write_size()
self._refill(write_size)

View File

@ -51,9 +51,6 @@ class SilentAudioPlayer(AbstractAudioPlayer):
def clear(self):
pass
def get_write_size(self):
pass
def write(self, audio_data, length):
pass

View File

@ -3,7 +3,7 @@ import platform
import os
from pyglet.libs.win32.constants import *
from pyglet.libs.win32.types import *
from pyglet import com
from pyglet.libs.win32 import com
from pyglet.util import debug_print
_debug = debug_print('debug_media')

View File

@ -152,10 +152,7 @@ class PlayerWorkerThread(MediaThread):
if self.players:
filled = False
for player in list(self.players):
write_size = player.get_write_size()
if write_size > player.min_buffer_size:
player.refill(write_size)
filled = True
filled = player.refill_buffer()
if not filled:
sleep_time = self._nap_time
else:

View File

@ -427,6 +427,9 @@ class Player(pyglet.event.EventDispatcher):
"""
return self.texture
def refill_buffer(self):
raise NotImplemented
def seek_next_frame(self):
"""Step forwards one video frame in the current source."""
time = self.source.get_next_video_timestamp()
@ -634,7 +637,7 @@ class Player(pyglet.event.EventDispatcher):
def on_driver_reset(self):
"""The audio driver has been reset, by default this will kill the current audio player and create a new one,
and requeue the buffers. Any buffers that may have been queued in a player will be resubmitted. It will
continue from from the last buffers submitted, not played and may cause sync issues if using video.
continue from the last buffers submitted, not played and may cause sync issues if using video.
:event:
"""

View File

@ -33,12 +33,12 @@
# POSSIBILITY OF SUCH DAMAGE.
# ----------------------------------------------------------------------------
import os
import math
import ctypes
import struct
import math as _math
import struct as _struct
from .codecs.base import Source, AudioFormat, AudioData
from random import uniform as _uniform
from pyglet.media.codecs.base import Source, AudioFormat, AudioData
# Envelope classes:
@ -168,9 +168,9 @@ class TremoloEnvelope(_Envelope):
period = total_bytes / duration
max_amplitude = self.amplitude
min_amplitude = max(0.0, (1.0 - self.depth) * self.amplitude)
step = (math.pi * 2) / period / self.rate
step = (_math.pi * 2) / period / self.rate
for i in range(total_bytes):
value = math.sin(step * i)
value = _math.sin(step * i)
yield value * (max_amplitude - min_amplitude) + min_amplitude
while True:
yield 0
@ -178,35 +178,51 @@ class TremoloEnvelope(_Envelope):
# Waveform generators
def sine_generator(frequency, sample_rate, offset=0):
step = 2 * math.pi * frequency
i = offset
def silence_generator(frequency, sample_rate):
while True:
yield math.sin(step * i / sample_rate)
yield 0
def noise_generator(frequency, sample_rate):
while True:
yield _uniform(-1, 1)
def sine_generator(frequency, sample_rate):
step = 2 * _math.pi * frequency
i = 0
while True:
yield _math.sin(step * i / sample_rate)
i += 1
# def triangle_generator(frequency, sample_rate, offset=0):
# period_length = int(sample_rate / frequency)
# half_period = period_length / 2
# one_period = [1 / half_period * (half_period - abs(i - half_period) * 2 - 1) + 0.02
# for i in range(period_length)]
# return itertools.cycle(one_period)
#
#
# def sawtooth_generator(frequency, sample_rate, offset=0):
# i = offset
# while True:
# yield frequency * i * 2 - 1
# i += 1 / sample_rate
# if i > 1:
# i = 0
def triangle_generator(frequency, sample_rate):
step = 4 * frequency / sample_rate
value = 0
while True:
if value > 1:
value = 1 - (value - 1)
step = -step
if value < -1:
value = -1 - (value - -1)
step = -step
yield value
value += step
def pulse_generator(frequency, sample_rate, offset=0, duty_cycle=50):
def sawtooth_generator(frequency, sample_rate):
period_length = int(sample_rate / frequency)
step = 2 * frequency / sample_rate
i = 0
while True:
yield step * (i % period_length) - 1
i += 1
def pulse_generator(frequency, sample_rate, duty_cycle=50):
period_length = int(sample_rate / frequency)
duty_cycle = int(duty_cycle * period_length / 100)
i = offset
i = 0
while True:
yield int(i % period_length < duty_cycle) * 2 - 1
i += 1
@ -214,68 +230,11 @@ def pulse_generator(frequency, sample_rate, offset=0, duty_cycle=50):
# Source classes:
class SynthesisSource(Source):
"""Base class for synthesized waveforms.
:Parameters:
`duration` : float
The length, in seconds, of audio that you wish to generate.
`sample_rate` : int
Audio samples per second. (CD quality is 44100).
"""
def __init__(self, duration, sample_rate=44800, envelope=None):
self._duration = float(duration)
self.audio_format = AudioFormat(channels=1, sample_size=16, sample_rate=sample_rate)
self._offset = 0
self._sample_rate = sample_rate
self._bytes_per_sample = 2
self._bytes_per_second = self._bytes_per_sample * sample_rate
self._max_offset = int(self._bytes_per_second * self._duration)
self.envelope = envelope or FlatEnvelope(amplitude=1.0)
self._envelope_generator = self.envelope.get_generator(sample_rate, duration)
# Align to sample:
self._max_offset &= 0xfffffffe
def get_audio_data(self, num_bytes, compensation_time=0.0):
"""Return `num_bytes` bytes of audio data."""
num_bytes = min(num_bytes, self._max_offset - self._offset)
if num_bytes <= 0:
return None
timestamp = float(self._offset) / self._bytes_per_second
duration = float(num_bytes) / self._bytes_per_second
data = self._generate_data(num_bytes)
self._offset += num_bytes
return AudioData(data, num_bytes, timestamp, duration, [])
def _generate_data(self, num_bytes):
"""Generate `num_bytes` bytes of data.
Return data as ctypes array or bytes.
"""
raise NotImplementedError('abstract')
def seek(self, timestamp):
self._offset = int(timestamp * self._bytes_per_second)
# Bound within duration
self._offset = min(max(self._offset, 0), self._max_offset)
# Align to sample
self._offset &= 0xfffffffe
self._envelope_generator = self.envelope.get_generator(self._sample_rate, self._duration)
class _SynthesisSource(Source):
"""Base class for synthesized waveforms.
:Parameters:
`generator` : generator
`generator` : A non-instantiated generator object
A waveform generator that produces a stream of numbers from (-1, 1)
`duration` : float
The length, in seconds, of audio that you wish to generate.
@ -286,11 +245,11 @@ class _SynthesisSource(Source):
`envelope` : :py:class:`pyglet.media.synthesis._Envelope`
An optional Envelope to apply to the waveform.
"""
def __init__(self, generator, duration, frequency=440, sample_rate=44800, envelope=None):
self._generator_function = generator
self._generator = generator(frequency, sample_rate)
self._duration = float(duration)
def __init__(self, generator, duration, frequency, sample_rate, envelope=None):
self._generator = generator
self._duration = duration
self._frequency = frequency
self.envelope = envelope or FlatEnvelope(amplitude=1.0)
self._envelope_generator = self.envelope.get_generator(sample_rate, duration)
@ -300,7 +259,7 @@ class _SynthesisSource(Source):
self._sample_rate = sample_rate
self._bytes_per_sample = 2
self._bytes_per_second = self._bytes_per_sample * sample_rate
self._max_offset = int(self._bytes_per_second * self._duration)
self._max_offset = int(self._bytes_per_second * duration)
# Align to sample:
self._max_offset &= 0xfffffffe
@ -312,9 +271,10 @@ class _SynthesisSource(Source):
timestamp = float(self._offset) / self._bytes_per_second
duration = float(num_bytes) / self._bytes_per_second
data = self._generate_data(num_bytes)
self._offset += num_bytes
data = self._generate_data(num_bytes)
return AudioData(data, num_bytes, timestamp, duration, [])
def _generate_data(self, num_bytes):
@ -323,7 +283,7 @@ class _SynthesisSource(Source):
generator = self._generator
envelope = self._envelope_generator
data = (int(next(generator) * next(envelope) * amplitude) for _ in range(samples))
return struct.pack(f"{samples}h", *data)
return _struct.pack(f"{samples}h", *data)
def seek(self, timestamp):
self._offset = int(timestamp * self._bytes_per_second)
@ -333,115 +293,54 @@ class _SynthesisSource(Source):
# Align to sample
self._offset &= 0xfffffffe
self._envelope_generator = self.envelope.get_generator(self._sample_rate, self._duration)
self._generator = self._generator_function(self._frequency, self._sample_rate, self._offset)
class Silence(SynthesisSource):
"""A silent waveform."""
def _generate_data(self, num_bytes):
return b'\0' * num_bytes
class Silence(_SynthesisSource):
def __init__(self, duration, frequency=440, sample_rate=44800, envelope=None):
"""Create a Silent waveform."""
super().__init__(silence_generator(frequency, sample_rate), duration, frequency, sample_rate, envelope)
class WhiteNoise(SynthesisSource):
"""A white noise, random waveform."""
def _generate_data(self, num_bytes):
# TODO; use envelope
return os.urandom(num_bytes)
class WhiteNoise(_SynthesisSource):
def __init__(self, duration, frequency=440, sample_rate=44800, envelope=None):
"""Create a random white noise waveform."""
super().__init__(noise_generator(frequency, sample_rate), duration, frequency, sample_rate, envelope)
class Sine(_SynthesisSource):
def __init__(self, duration, frequency=440, sample_rate=44800, envelope=None):
"""Create a sinusoid (sine) waveform."""
super().__init__(sine_generator, duration, frequency, sample_rate, envelope)
super().__init__(sine_generator(frequency, sample_rate), duration, frequency, sample_rate, envelope)
class Square(_SynthesisSource):
def __init__(self, duration, frequency=440, sample_rate=44800, envelope=None):
"""Create a Square (pulse) waveform."""
super().__init__(pulse_generator, duration, frequency, sample_rate, envelope)
super().__init__(pulse_generator(frequency, sample_rate), duration, frequency, sample_rate, envelope)
class Triangle(SynthesisSource):
"""A triangle waveform.
:Parameters:
`duration` : float
The length, in seconds, of audio that you wish to generate.
`frequency` : int
The frequency, in Hz of the waveform you wish to produce.
`sample_rate` : int
Audio samples per second. (CD quality is 44100).
"""
def __init__(self, duration, frequency=440, **kwargs):
super().__init__(duration, **kwargs)
self.frequency = frequency
def _generate_data(self, num_bytes):
samples = num_bytes >> 1
value = 0
maximum = 32767
minimum = -32768
data = (ctypes.c_short * samples)()
step = (maximum - minimum) * 2 * self.frequency / self.audio_format.sample_rate
envelope = self._envelope_generator
for i in range(samples):
value += step
if value > maximum:
value = maximum - (value - maximum)
step = -step
if value < minimum:
value = minimum - (value - minimum)
step = -step
data[i] = int(value * next(envelope))
return bytes(data)
class Triangle(_SynthesisSource):
def __init__(self, duration, frequency=440, sample_rate=44800, envelope=None):
"""Create a Triangle waveform."""
super().__init__(triangle_generator(frequency, sample_rate), duration, frequency, sample_rate, envelope)
class Sawtooth(SynthesisSource):
"""A sawtooth waveform.
:Parameters:
`duration` : float
The length, in seconds, of audio that you wish to generate.
`frequency` : int
The frequency, in Hz of the waveform you wish to produce.
`sample_rate` : int
Audio samples per second. (CD quality is 44100).
"""
def __init__(self, duration, frequency=440, **kwargs):
super().__init__(duration, **kwargs)
self.frequency = frequency
def _generate_data(self, num_bytes):
samples = num_bytes >> 1
value = 0
maximum = 32767
minimum = -32768
data = (ctypes.c_short * samples)()
step = (maximum - minimum) * self.frequency / self._sample_rate
envelope = self._envelope_generator
for i in range(samples):
value += step
if value > maximum:
value = minimum + (value % maximum)
data[i] = int(value * next(envelope))
return bytes(data)
class Sawtooth(_SynthesisSource):
def __init__(self, duration, frequency=440, sample_rate=44800, envelope=None):
"""Create a Sawtooth waveform."""
super().__init__(sawtooth_generator(frequency, sample_rate), duration, frequency, sample_rate, envelope)
#############################################
# Experimental multi-operator FM synthesis:
#############################################
def operator(samplerate=44800, frequency=440, index=1, modulator=None, envelope=None):
def sine_operator(samplerate=44800, frequency=440, index=1, modulator=None, envelope=None):
# A sine generator that can be optionally modulated with another generator.
# FM equation: sin((i * 2 * pi * carrier_frequency) + sin(i * 2 * pi * modulator_frequency))
sin = math.sin
step = 2 * math.pi * frequency / samplerate
sin = _math.sin
step = 2 * _math.pi * frequency / samplerate
i = 0
envelope = envelope or FlatEnvelope(1).get_generator(samplerate, duration=None)
if modulator:
@ -450,7 +349,7 @@ def operator(samplerate=44800, frequency=440, index=1, modulator=None, envelope=
i += 1
else:
while True:
yield math.sin(i * step) * next(envelope)
yield _math.sin(i * step) * next(envelope)
i += 1
@ -458,20 +357,6 @@ def composite_operator(*operators):
return (sum(samples) / len(samples) for samples in zip(*operators))
class Encoder(SynthesisSource):
def __init__(self, duration, operator, **kwargs):
super().__init__(duration, **kwargs)
self._operator = operator
self._total = int(duration * self.audio_format.sample_rate)
self._consumed = 0
def _generate_data(self, num_bytes):
envelope = self._envelope_generator
generator = self._operator
samples = num_bytes >> 1
amplitude = 32767
data = (int(next(generator) * amplitude * next(envelope)) for i in range(samples))
return struct.pack(f"{samples}h", *data)
class Encoder(_SynthesisSource):
def __init__(self, operator, duration, frequency=440, sample_rate=44800, envelope=None):
super().__init__(operator, duration, frequency, sample_rate, envelope)

View File

@ -37,6 +37,7 @@ import os
import pyglet
from pyglet.gl import GL_TRIANGLES
from pyglet.util import asstr
from .. import Model, Material, MaterialGroup, TexturedMaterialGroup
from . import ModelDecodeException, ModelDecoder
@ -121,17 +122,14 @@ def parse_obj_file(filename, file=None):
location = os.path.dirname(filename)
if file is None:
with open(filename, 'r') as f:
file_contents = f.read()
else:
file_contents = file.read()
if hasattr(file_contents, 'decode'):
try:
file_contents = file_contents.decode()
except UnicodeDecodeError as e:
raise ModelDecodeException("Unable to decode obj: {}".format(e))
try:
if file is None:
with open(filename, 'r') as f:
file_contents = f.read()
else:
file_contents = asstr(file.read())
except (UnicodeDecodeError, OSError):
raise ModelDecodeException
material = None
mesh = None

View File

@ -943,6 +943,27 @@ class BaseWindow(with_metaclass(_WindowMetaclass, EventDispatcher)):
def height(self, new_height):
self.set_size(self.width, new_height)
@property
def size(self):
"""The size of the window. Read-Write.
:type: tuple
"""
return self.get_size()
@size.setter
def size(self, new_size):
self.set_size(*new_size)
@property
def aspect_ratio(self):
"""The aspect ratio of the window. Read-Only.
:type: float
"""
w, h = self.get_size()
return w / h
@property
def projection(self):
"""The OpenGL window projection matrix. Read-write.

View File

@ -56,9 +56,6 @@ class HeadlessWindow(BaseWindow):
_egl_display_connection = None
_egl_surface = None
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def _recreate(self, changes):
pass