ruaaa update!

This commit is contained in:
shenjackyuanjie 2022-05-25 09:16:38 +08:00
parent 2d2e2158a7
commit 9288423d54
20 changed files with 373 additions and 192 deletions

View File

@ -11,13 +11,8 @@ github: @shenjackyuanjie
gitee: @shenjackyuanjie
"""
from .command import CommandParseError, CommandQuotationMarkError
__all__ = ['TexturesError',
'LanguageError',
'CommandError',
'CommandParseError',
'CommandQuotationMarkError',
'TestError']
@ -31,11 +26,6 @@ class TexturesError(Error):
pass
class CommandError(Error):
"""命令解析相关 error"""
pass
class LanguageError(Error):
"""语言相关 error"""
pass

View File

@ -11,16 +11,22 @@ github: @shenjackyuanjie
gitee: @shenjackyuanjie
"""
from . import CommandError
from . import Error
class CommandError(Error):
"""命令解析相关 error"""
class CommandParseError(CommandError):
"""命令解析时出现错误"""
pass
class CommandQuotationMarkError(CommandParseError):
class CommandQuotationMarkPositionError(CommandParseError):
"""命令中,引号位置不正确
例如 /command "aabcc "awdawd
"""
pass
例如 /command "aabcc "awdawd"""
class CommandQuotationMarkMissing(CommandParseError):
"""命令中引号缺失
例如: /command "aawwdawda awdaw """

View File

@ -15,18 +15,20 @@ gitee: @shenjackyuanjie
import os
import sys
import time
import ctypes
import logging
import traceback
from decimal import Decimal
# Difficult_Rocket function
from Difficult_Rocket.api.Exp import *
from Difficult_Rocket.command import line, tree
from Difficult_Rocket.api import new_thread
from Difficult_Rocket.utils.translate import tr
from Difficult_Rocket.guis.widgets import InputBox
# from Difficult_Rocket.client.screen import DRScreen
from Difficult_Rocket.utils import tools, translate
from Difficult_Rocket.api.Exp.command import CommandError
from Difficult_Rocket.client.fps.fps_log import FpsLogger
# libs function
@ -115,6 +117,7 @@ class ClientWindow(Window):
# frame
self.frame = pyglet.gui.Frame(self, order=20)
self.M_frame = pyglet.gui.MovableFrame(self, modifier=key.LCTRL)
# self.DRscreen = DRScreen(self)
# setup
self.setup()
# 命令显示

View File

@ -7,19 +7,21 @@
import ctypes
from Difficult_Rocket.client import ClientWindow
from Difficult_Rocket.command.tree import CommandTree
class BaseScreen:
def __init__(self, main_window: ClientWindow):
self.main_window_pointer = ctypes.pointer(main_window)
def __init__(self, main_window: "ClientWindow"):
self.command_tree = None
self.create_command_tree()
def update(self, tick: float):
pass
def create_command_tree(self):
pass
self.command_tree = CommandTree({})
class DRScreen(BaseScreen):
def __init__(self, main_window: ClientWindow):
def __init__(self, main_window: "ClientWindow"):
super().__init__(main_window)

View File

@ -14,10 +14,10 @@ gitee: @shenjackyuanjie
# system function
import re
from typing import Union
from typing import Union, Optional, Type, Tuple
# DR
from Difficult_Rocket.api.Exp import CommandQuotationMarkError
from Difficult_Rocket.api.Exp.command import *
search_re = re.compile(r'(?<!\\)"')
@ -36,15 +36,16 @@ class CommandText:
self.command_tree = {}
tree_list = text.split(' ')
pass_node = False
for node in tree_list:
if node[0] == "\"" and len(node) > 1: # |"xxxxx|
if pass_node: # |"xxxx "xxxx|
self.error = CommandQuotationMarkError
pass_node = True
first_node = tree_list.index(node)
if node[-1] == "\"" and len(node) > 1: # |xxxxxx"|
pass_node = False
self.tree_node = tree_list
@staticmethod
def parse_command(raw_command: Union[str, "CommandText"]) -> Tuple[list, Optional[Type[CommandParseError]]]:
spilt_list = str(raw_command).split(" ")
for spilted in spilt_list:
pass
return spilt_list, CommandQuotationMarkPositionError
def find(self, text: str) -> Union[str, bool]:
finding = re.match(text, self.text)

View File

@ -25,6 +25,8 @@
- 将 `api/Exp` 改为文件夹 `api/Exp/__init__.py`
- 再次重写了 `client.load_fonts()` 现在改为直接加载单独的 `pyglet_load_fonts_folder()`
- 更新了 `command/` 里的一大堆东西
- 退钱!开摆!

View File

@ -200,7 +200,7 @@ class GlyphTextureAtlas(image.atlas.TextureAtlas):
texture_class = GlyphTexture
def __init__(self, width=2048, height=2048, fmt=GL_RGBA, min_filter=GL_LINEAR, mag_filter=GL_LINEAR):
self.texture = self.texture_class.create(width, height, GL_TEXTURE_2D, fmt, min_filter, mag_filter)
self.texture = self.texture_class.create(width, height, GL_TEXTURE_2D, fmt, min_filter, mag_filter, fmt=fmt)
self.allocator = image.atlas.Allocator(width, height)
@ -306,7 +306,7 @@ class Font:
"""
return True
def create_glyph(self, image):
def create_glyph(self, image, fmt=None):
"""Create a glyph using the given image.
This is used internally by `Font` subclasses to add glyph data
@ -320,6 +320,8 @@ class Font:
:Parameters:
`image` : `pyglet.image.AbstractImage`
The image to write to the font texture.
`fmt` : `int`
Override for the format and internalformat of the atlas texture
:rtype: `Glyph`
"""
@ -329,7 +331,7 @@ class Font:
self.texture_bin = GlyphTextureBin(self.texture_width, self.texture_height)
glyph = self.texture_bin.add(
image, self.texture_internalformat, self.texture_min_filter, self.texture_mag_filter, border=1)
image, fmt or self.texture_internalformat, self.texture_min_filter, self.texture_mag_filter, border=1)
return glyph

View File

@ -32,10 +32,17 @@
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# ----------------------------------------------------------------------------
from enum import Enum
from pyglet import gl
from pyglet.gl import gl_info
class OpenGLAPI(Enum):
OPENGL = 1
OPENGL_ES = 2
class Config:
"""Graphics configuration.
@ -106,12 +113,14 @@ class Config:
'major_version',
'minor_version',
'forward_compatible',
'opengl_api',
'debug'
]
major_version = None
minor_version = None
forward_compatible = None
opengl_api = None
debug = None
def __init__(self, **kwargs):
@ -211,6 +220,7 @@ class CanvasConfig(Config):
self.major_version = base_config.major_version
self.minor_version = base_config.minor_version
self.forward_compatible = base_config.forward_compatible
self.opengl_api = base_config.opengl_api
self.debug = base_config.debug
def compatible(self, canvas):

View File

@ -56,10 +56,11 @@ context::
"""
from ctypes import c_char_p, cast
from ctypes import c_char_p, cast, c_int
import warnings
from pyglet.gl.gl import GL_EXTENSIONS, GL_RENDERER, GL_VENDOR, GL_VERSION
from pyglet.gl.gl import (GL_EXTENSIONS, GL_RENDERER, GL_VENDOR,
GL_VERSION, GL_MAJOR_VERSION, GL_MINOR_VERSION)
from pyglet.util import asstr
@ -92,7 +93,16 @@ class GLInfo:
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)
@ -132,7 +142,17 @@ class GLInfo:
def get_version(self):
"""Get the current OpenGL version.
:return: the OpenGL version
:return: The major and minor version as a tuple
:rtype: tuple
"""
if not self.have_context:
warnings.warn('No GL context created yet.')
return self.major_version, self.minor_version
def get_version_string(self):
"""Get the current OpenGL version string.
:return: The OpenGL version string
:rtype: str
"""
if not self.have_context:
@ -154,13 +174,12 @@ class GLInfo:
if not self.have_context:
warnings.warn('No GL context created yet.')
if not self.version or 'None' in self.version:
if not self.major_version and not self.minor_version:
return False
ver = '%s.0' % self.version.split(' ', 1)[0]
imajor, iminor = [int(v) for v in ver.split('.', 3)[:2]]
return (imajor > major or
(imajor == major and iminor >= minor) or
(imajor == major and iminor == minor))
return (self.major_version > major or
(self.major_version == major and self.minor_version >= minor) or
(self.major_version == major and self.minor_version == minor))
def get_renderer(self):
"""Determine the renderer string of the OpenGL context.
@ -180,6 +199,16 @@ class GLInfo:
warnings.warn('No GL context created yet.')
return self.vendor
def get_opengl_api(self):
"""Determine the OpenGL API version.
Usually ``gl`` or ``gles``.
:rtype: str
"""
if not self.have_context:
warnings.warn('No GL context created yet.')
return self.opengl_api
# Single instance useful for apps with only a single context
# (or all contexts have the same GL driver, a common case).
@ -190,10 +219,11 @@ 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
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.

View File

@ -36,12 +36,12 @@
import warnings
from ctypes import *
from .base import Config, CanvasConfig, Context
from pyglet import gl
from pyglet.canvas.headless import HeadlessCanvas
from pyglet.libs.egl import egl
from pyglet.libs.egl.egl import *
from pyglet import gl
from .base import CanvasConfig, Config, Context
_fake_gl_attributes = {
'double_buffer': 0,
@ -69,7 +69,10 @@ class HeadlessConfig(Config):
if attr and value is not None:
attrs.extend([attr, int(value)])
attrs.extend([EGL_SURFACE_TYPE, EGL_PBUFFER_BIT])
attrs.extend([EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT])
if self.opengl_api == "gl":
attrs.extend([EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT])
elif self.opengl_api == "gles":
attrs.extend([EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT])
attrs.extend([EGL_NONE])
attrs_list = (egl.EGLint * len(attrs))(*attrs)
@ -139,7 +142,10 @@ class HeadlessContext(Context):
else:
share_context = None
egl.eglBindAPI(egl.EGL_OPENGL_API)
if self.config.opengl_api == "gl":
egl.eglBindAPI(egl.EGL_OPENGL_API)
elif self.config.opengl_api == "gles":
egl.eglBindAPI(egl.EGL_OPENGL_ES_API)
return egl.eglCreateContext(self.config.canvas.display._display_connection,
self.config._egl_config, share_context,
self.config._context_attrib_array)

View File

@ -36,7 +36,7 @@
import warnings
from ctypes import *
from .base import Config, CanvasConfig, Context
from .base import Config, CanvasConfig, Context, OpenGLAPI
from pyglet.canvas.xlib import XlibCanvas
from pyglet.gl import glx
from pyglet.gl import glxext_arb
@ -390,11 +390,18 @@ class XlibContextARB(XlibContext13):
if self.config.minor_version is not None:
attribs.extend([glxext_arb.GLX_CONTEXT_MINOR_VERSION_ARB,
self.config.minor_version])
if self.config.opengl_api == "gl":
attribs.extend([glxext_arb.GLX_CONTEXT_PROFILE_MASK_ARB, glxext_arb.GLX_CONTEXT_CORE_PROFILE_BIT_ARB])
elif self.config.opengl_api == "gles":
attribs.extend([glxext_arb.GLX_CONTEXT_PROFILE_MASK_ARB, glxext_arb.GLX_CONTEXT_ES2_PROFILE_BIT_EXT])
flags = 0
if self.config.forward_compatible:
flags |= glxext_arb.GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB
if self.config.debug:
flags |= glxext_arb.GLX_CONTEXT_DEBUG_BIT_ARB
if flags:
attribs.extend([glxext_arb.GLX_CONTEXT_FLAGS_ARB, flags])
attribs.append(0)

View File

@ -1,3 +1,4 @@
from typing import Dict, List
from ctypes import *
from weakref import proxy
@ -201,7 +202,7 @@ class Shader:
raise TypeError("The `shader_type` '{}' is not yet supported".format(shader_type))
self.type = shader_type
source_string = source_string.strip()
source_string = ShaderSource(source_string, _shader_types[shader_type]).validate()
shader_source_utf8 = source_string.encode("utf8")
source_buffer_pointer = cast(c_char_p(shader_source_utf8), POINTER(c_char))
source_length = c_int(len(shader_source_utf8))
@ -697,3 +698,61 @@ 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

@ -241,7 +241,7 @@ class BufferObject(AbstractBuffer):
temp = (ctypes.c_byte * size)()
glBindBuffer(self.target, self.id)
data = glMapBuffer(self.target, GL_READ_ONLY)
data = glMapBufferRange(self.target, 0, self.size, GL_MAP_READ_BIT)
ctypes.memmove(temp, data, min(size, self.size))
glUnmapBuffer(self.target)

View File

@ -210,7 +210,7 @@ class PushButton(WidgetBase):
self._pressed = False
def _update_position(self):
self._sprite.position = self._x, self._y
self._sprite.position = self._x, self._y, 0
@property
def value(self):

View File

@ -1249,7 +1249,7 @@ class Texture(AbstractImage):
pass
@classmethod
def create(cls, width, height, target=GL_TEXTURE_2D, internalformat=GL_RGBA, min_filter=None, mag_filter=None):
def create(cls, width, height, target=GL_TEXTURE_2D, internalformat=GL_RGBA8, min_filter=None, mag_filter=None, fmt=GL_RGBA):
"""Create a Texture
Create a Texture with the specified dimentions, target and format.
@ -1264,11 +1264,16 @@ class Texture(AbstractImage):
GL constant giving texture target to use, typically ``GL_TEXTURE_2D``.
`internalformat` : int
GL constant giving internal format of texture; for example, ``GL_RGBA``.
The internal format decides how the texture data will be stored internally.
If ``None``, the texture will be created but not initialized.
`min_filter` : int
The minifaction filter used for this texture, commonly ``GL_LINEAR`` or ``GL_NEAREST``
`mag_filter` : int
The magnification filter used for this texture, commonly ``GL_LINEAR`` or ``GL_NEAREST``
`fmt` : int
GL constant giving format of texture; for example, ``GL_RGBA``.
The format specifies what format the pixel data we're expecting to write
to the texture and should ideally be the same as for internal format.
:rtype: :py:class:`~pyglet.image.Texture`
"""
@ -1287,7 +1292,8 @@ class Texture(AbstractImage):
internalformat,
width, height,
0,
GL_RGBA, GL_UNSIGNED_BYTE,
fmt,
GL_UNSIGNED_BYTE,
blank)
glFlush()
@ -1320,9 +1326,22 @@ class Texture(AbstractImage):
fmt = 'RGBA'
gl_format = GL_RGBA
glPixelStorei(GL_PACK_ALIGNMENT, 1)
buf = (GLubyte * (self.width * self.height * self.images * len(fmt)))()
glGetTexImage(self.target, self.level, gl_format, GL_UNSIGNED_BYTE, buf)
# TODO: Clean up this temporary hack
if gl.current_context.get_info().get_opengl_api() == "gles":
fbo = c_uint()
glGenFramebuffers(1, fbo)
glBindFramebuffer(GL_FRAMEBUFFER, fbo.value)
glPixelStorei(GL_PACK_ALIGNMENT, 1)
glCheckFramebufferStatus(GL_FRAMEBUFFER)
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, self.id, self.level)
glReadPixels(0, 0, self.width, self.height, gl_format, GL_UNSIGNED_BYTE, buf)
glBindFramebuffer(GL_FRAMEBUFFER, 0)
glDeleteFramebuffers(1, fbo)
else:
glPixelStorei(GL_PACK_ALIGNMENT, 1)
glGetTexImage(self.target, self.level, gl_format, GL_UNSIGNED_BYTE, buf)
data = ImageData(self.width, self.height, fmt, buf)
if self.images > 1:

View File

@ -211,4 +211,4 @@ else:
from .darwin_hid import get_joysticks
from .darwin_hid import get_apple_remote
from .darwin_hid import get_controllers
# TODO: create ControllerManager for OSX
from .darwin_hid import DarwinControllerManager as ControllerManager

View File

@ -39,12 +39,13 @@ from ctypes import CFUNCTYPE, byref, c_void_p, c_int, c_ubyte, c_bool, c_uint32,
from .controller import get_mapping
from .base import Device, AbsoluteAxis, RelativeAxis, Button
from .base import Joystick, Controller, AppleRemote
from .base import Joystick, Controller, AppleRemote, ControllerManager
from pyglet.libs.darwin.cocoapy import CFSTR, CFIndex, CFTypeID, known_cftypes
from pyglet.libs.darwin.cocoapy import kCFRunLoopDefaultMode, CFAllocatorRef, cf
from pyglet.libs.darwin.cocoapy import cfset_to_set, cftype_to_value, cfarray_to_list
from pyglet.lib import load_library
from pyglet.event import EventDispatcher
__LP64__ = (sys.maxsize > 2 ** 32)
@ -465,7 +466,7 @@ class HIDDeviceElement:
self.physicalMax = iokit.IOHIDElementGetPhysicalMax(elementRef)
class HIDManager:
class HIDManager(EventDispatcher):
def __init__(self):
# Create the HID Manager.
self.managerRef = c_void_p(iokit.IOHIDManagerCreate(None, kIOHIDOptionsTypeNone))
@ -521,6 +522,10 @@ class HIDManager:
return matching_callback
HIDManager.register_event_type('on_connect')
HIDManager.register_event_type('on_disconnect')
######################################################################
# Add conversion methods for IOHIDDevices and IOHIDDeviceElements
# to the list of known types used by cftype_to_value.
@ -565,11 +570,12 @@ _button_names = {
class PygletDevice(Device):
def __init__(self, display, device, manager):
super(PygletDevice, self).__init__(display=display, name=device.product)
super().__init__(display=display, name=device.product)
self.device = device
self.device_identifier = self.device.unique_identifier()
self.device.add_value_observer(self)
self.device.add_removal_observer(self)
self._manager = manager
manager.matching_observers.add(self)
self._create_controls()
self._is_open = False
@ -619,6 +625,7 @@ class PygletDevice(Device):
# Set device to None, but Keep self._controls around
# in case device is plugged back in.
self.device = None
self._manager.dispatch_event('on_disconnect', self)
def device_discovered(self, hid_device):
# Called by HID manager when new device is found.
@ -632,6 +639,7 @@ class PygletDevice(Device):
if self._is_open:
self.device.open(self._is_exclusive)
self.device.schedule_with_run_loop()
self._manager.dispatch_event('on_connect', self)
def device_value_changed(self, hid_device, hid_value):
# Called by device when input value changes.
@ -671,32 +679,55 @@ class PygletDevice(Device):
######################################################################
_manager = HIDManager()
_hid_manager = HIDManager()
class DarwinControllerManager(ControllerManager):
def __init__(self, display=None):
self._display = display
self._controllers = {}
for device in _hid_manager.devices:
controller = _create_controller(device, display)
if controller:
self._controllers[device] = controller
@_hid_manager.event
def on_connect(hiddevice):
self.dispatch_event('on_connect', self._controllers[hiddevice])
@_hid_manager.event
def on_disconnect(hiddevice):
self.dispatch_event('on_disconnect', self._controllers[hiddevice])
def get_controllers(self):
pass
def get_devices(display=None):
return [PygletDevice(display, device, _manager) for device in _manager.devices]
return [PygletDevice(display, device, _hid_manager) for device in _hid_manager.devices]
def get_joysticks(display=None):
return [Joystick(PygletDevice(display, device, _manager)) for device in _manager.devices
return [Joystick(PygletDevice(display, device, _hid_manager)) for device in _hid_manager.devices
if device.is_joystick() or device.is_gamepad() or device.is_multi_axis()]
def get_apple_remote(display=None):
for device in _manager.devices:
for device in _hid_manager.devices:
if device.product == 'Apple IR':
return AppleRemote(PygletDevice(display, device, _manager))
return AppleRemote(PygletDevice(display, device, _hid_manager))
def _create_controller(device, display):
mapping = get_mapping(device.get_guid())
if not mapping:
return
return Controller(PygletDevice(display, device, _manager), mapping)
return Controller(PygletDevice(display, device, _hid_manager), mapping)
def get_controllers(display=None):
return [controller for controller in
[_create_controller(device, display) for device in _manager.devices]
[_create_controller(device, display) for device in _hid_manager.devices]
if controller is not None]

View File

@ -56,7 +56,7 @@ class FlatEnvelope(_Envelope):
:Parameters:
`amplitude` : float
The amplitude (volume) of the wave, from 0.0 to 1.0.
Values outside of this range will be clamped.
Values outside this range will be clamped.
"""
def __init__(self, amplitude=0.5):
@ -77,7 +77,7 @@ class LinearDecayEnvelope(_Envelope):
:Parameters:
`peak` : float
The Initial peak value of the envelope, from 0.0 to 1.0.
Values outside of this range will be clamped.
Values outside this range will be clamped.
"""
def __init__(self, peak=1.0):
@ -176,6 +176,42 @@ class TremoloEnvelope(_Envelope):
yield 0
# Waveform generators
def sine_generator(frequency, sample_rate, offset=0):
step = 2 * math.pi * frequency
i = offset
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 pulse_generator(frequency, sample_rate, offset=0, duty_cycle=50):
period_length = int(sample_rate / frequency)
duty_cycle = int(duty_cycle * period_length / 100)
i = offset
while True:
yield int(i % period_length < duty_cycle) * 2 - 1
i += 1
# Source classes:
class SynthesisSource(Source):
@ -235,6 +271,73 @@ class SynthesisSource(Source):
self._envelope_generator = self.envelope.get_generator(self._sample_rate, self._duration)
class _SynthesisSource(Source):
"""Base class for synthesized waveforms.
:Parameters:
`generator` : generator
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.
`frequency` : float
The frequency, in Hz, of the waveform you wish to generate.
`sample_rate` : int
Audio samples per second. (CD quality is 44100).
`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)
self._frequency = frequency
self.envelope = envelope or FlatEnvelope(amplitude=1.0)
self._envelope_generator = self.envelope.get_generator(sample_rate, 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)
# 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):
samples = num_bytes >> 1
amplitude = 32767
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)
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)
self._generator = self._generator_function(self._frequency, self._sample_rate, self._offset)
class Silence(SynthesisSource):
"""A silent waveform."""
@ -246,34 +349,20 @@ class WhiteNoise(SynthesisSource):
"""A white noise, random waveform."""
def _generate_data(self, num_bytes):
# TODO; use envelope
return os.urandom(num_bytes)
class Sine(SynthesisSource):
"""A sinusoid (sine) waveform.
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)
: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
amplitude = 32767
data = (ctypes.c_short * samples)()
step = self.frequency * (math.pi * 2) / self.audio_format.sample_rate
envelope = self._envelope_generator
for i in range(samples):
data[i] = int(math.sin(step * i) * amplitude * next(envelope))
return bytes(data)
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)
class Triangle(SynthesisSource):
@ -344,88 +433,10 @@ class Sawtooth(SynthesisSource):
return bytes(data)
class Square(SynthesisSource):
"""A square (pulse) 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
amplitude = 32767
value = 1
count = 0
data = (ctypes.c_short * samples)()
half_period = self.audio_format.sample_rate / self.frequency / 2
envelope = self._envelope_generator
for i in range(samples):
if count >= half_period:
value = -value
count %= half_period
count += 1
data[i] = int(value * amplitude * next(envelope))
return bytes(data)
class SimpleFM(SynthesisSource):
"""A simple FM waveform.
This is a simplistic frequency modulated waveform, based on the
concepts by John Chowning. Basic sine waves are used for both
frequency carrier and modulator inputs, of which the frequencies can
be provided. The modulation index, or amplitude, can also be adjusted.
:Parameters:
`duration` : float
The length, in seconds, of audio that you wish to generate.
`carrier` : int
The carrier frequency, in Hz.
`modulator` : int
The modulator frequency, in Hz.
`mod_index` : int
The modulation index.
`sample_rate` : int
Audio samples per second. (CD quality is 44100).
"""
def __init__(self, duration, carrier=440, modulator=440, mod_index=1, **kwargs):
super().__init__(duration, **kwargs)
self.carrier = carrier
self.modulator = modulator
self.mod_index = mod_index
def _generate_data(self, num_bytes):
samples = num_bytes >> 1
amplitude = 32767
c_step = 2 * math.pi * self.carrier
m_step = 2 * math.pi * self.modulator
m_index = self.mod_index
sample_rate = self._sample_rate
envelope = self._envelope_generator
sin = math.sin
# FM equation: sin((2 * pi * carrier) + sin(2 * pi * modulator))
data = []
for i in range(samples):
increment = i / sample_rate
data.append(int(sin(c_step * increment + m_index * sin(m_step * increment)) * amplitude * next(envelope)))
return struct.pack(str(samples) + 'h', *data)
#############################################
# Experimental multi-operator FM synthesis:
#############################################
def 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))
@ -443,20 +454,20 @@ def operator(samplerate=44800, frequency=440, index=1, modulator=None, envelope=
i += 1
def composite_generator(*operators):
def composite_operator(*operators):
return (sum(samples) / len(samples) for samples in zip(*operators))
class Encoder(SynthesisSource):
def __init__(self, duration, generator, **kwargs):
def __init__(self, duration, operator, **kwargs):
super().__init__(duration, **kwargs)
self._generator = generator
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._generator
generator = self._operator
samples = num_bytes >> 1
amplitude = 32767

View File

@ -564,7 +564,7 @@ layout_fragment_source = """#version 330 core
out vec4 final_colors;
uniform sampler2D text;
uniform bool scissor = false;
uniform bool scissor;
uniform vec4 scissor_area;
void main()
@ -608,7 +608,7 @@ decoration_fragment_source = """#version 330 core
out vec4 final_colors;
uniform bool scissor = false;
uniform bool scissor;
uniform vec4 scissor_area;
void main()
@ -628,10 +628,10 @@ def get_default_layout_shader():
try:
return pyglet.gl.current_context.pyglet_text_layout_shader
except AttributeError:
_default_vert_shader = shader.Shader(layout_vertex_source, 'vertex')
_default_frag_shader = shader.Shader(layout_fragment_source, 'fragment')
default_shader_program = shader.ShaderProgram(_default_vert_shader, _default_frag_shader)
pyglet.gl.current_context.pyglet_text_layout_shader = default_shader_program
pyglet.gl.current_context.pyglet_text_layout_shader = shader.ShaderProgram(
shader.Shader(layout_vertex_source, 'vertex'),
shader.Shader(layout_fragment_source, 'fragment'),
)
return pyglet.gl.current_context.pyglet_text_layout_shader
@ -639,10 +639,10 @@ def get_default_decoration_shader():
try:
return pyglet.gl.current_context.pyglet_text_decoration_shader
except AttributeError:
_default_vert_shader = shader.Shader(decoration_vertex_source, 'vertex')
_default_frag_shader = shader.Shader(decoration_fragment_source, 'fragment')
default_shader_program = shader.ShaderProgram(_default_vert_shader, _default_frag_shader)
pyglet.gl.current_context.pyglet_text_decoration_shader = default_shader_program
pyglet.gl.current_context.pyglet_text_decoration_shader = shader.ShaderProgram(
shader.Shader(decoration_vertex_source, 'vertex'),
shader.Shader(decoration_fragment_source, 'fragment'),
)
return pyglet.gl.current_context.pyglet_text_decoration_shader

View File

@ -456,12 +456,14 @@ class BaseWindow(with_metaclass(_WindowMetaclass, EventDispatcher)):
}
"""
_default_fragment_source = """#version 150 core
out vec4 fragColor;
void main() {
fragColor = vec4(1.0);
}
out vec4 color;
void main()
{
color = vec4(1.0, 0.0, 0.0, 1.0);
}
"""
def __init__(self,
width=None,
height=None,
@ -619,8 +621,8 @@ class BaseWindow(with_metaclass(_WindowMetaclass, EventDispatcher)):
def _create_projection(self):
self._default_program = shader.ShaderProgram(
shader.Shader(self._default_vertex_source, 'vertex'),
shader.Shader(self._default_fragment_source, 'fragment'),
)
shader.Shader(self._default_fragment_source, 'fragment'))
self.ubo = self._default_program.uniform_blocks['WindowBlock'].create_ubo()
self._viewport = 0, 0, *self.get_framebuffer_size()