Skip to content

Commit

Permalink
Merge pull request #122 from Bubobubobubobubo/rewrite
Browse files Browse the repository at this point in the history
This is the big rewrite Sardine truly needed to start on the right track. Many many thanks to @thegamecracks for his invaluable contribution to the project. Now for new features :)
  • Loading branch information
Bubobubobubobubo authored Dec 8, 2022
2 parents fbb3174 + 26e9504 commit e5191ba
Show file tree
Hide file tree
Showing 102 changed files with 6,591 additions and 4,563 deletions.
File renamed without changes.
1 change: 1 addition & 0 deletions docs/documentation/sardinopedia/basic_swimming_lessons.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ In addition to that, take note of the `play()` method used for assigning a **Sen
* `play(*args, **kwargs)`: the default **SuperDirt** (or **S**) Sender.
* `play_midi(*args, **kwargs)`: the default **MIDI** (or **M**) Sender.
* `play_osc(*args, **kwargs)`: the default **OSC** (or **O**) Sender.
* `run(func: Callable)`: run any function like if it was a surfboard!

I repeat, these functions are basically senders with a different name! You will have to learn how to use **Senders** to be truly efficient with the surfing mode. You can spend your life using **Sardine** this way or combine it with *swimming functions*, this is entirely up to you! This mode was initially designed in order to demonstrate the syntax of [FoxDot](https://foxdot.org). I find it to be a fun and efficient way to jam along with friends as well :) You can just fire up a **Sardine** session and write pretty fast.

Expand Down
31 changes: 18 additions & 13 deletions fishery/UserConfig.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from dataclasses import dataclass
import json
from dataclasses import dataclass
from pathlib import Path
from typing import Union

from appdirs import *
from rich import print

Expand All @@ -21,11 +22,12 @@
"midi": None,
"bpm": 125,
"beats": 4,
"ppqn": 48,
"parameters": [],
"boot_superdirt": True,
"superdirt_handler": True,
"boot_supercollider": False,
"sardine_boot_file": True,
"verbose_superdirt": False,
"active_clock": True,
"link_clock": False,
"superdirt_config_path": str(USER_DIR / "default_superdirt.scd"),
"user_config_path": str(USER_DIR / "user_configuration.py"),
"deferred_scheduling": True,
Expand All @@ -48,13 +50,14 @@ class Config:
midi: Union[str, None]
beats: int
parameters: list
ppqn: int
bpm: int
superdirt_config_path: str
verbose_superdirt: bool
user_config_path: str
boot_superdirt: bool
active_clock: bool
boot_supercollider: bool
superdirt_handler: bool
sardine_boot_file: bool
link_clock: bool
deferred_scheduling: bool

@classmethod
Expand All @@ -64,11 +67,12 @@ def from_dict(cls, data: dict) -> "Config":
midi=config["midi"],
beats=config["beats"],
parameters=config["parameters"],
ppqn=config["ppqn"],
bpm=config["bpm"],
boot_superdirt=config["boot_superdirt"],
superdirt_handler=config["superdirt_handler"],
boot_supercollider=config["boot_supercollider"],
sardine_boot_file=config["sardine_boot_file"],
verbose_superdirt=config["verbose_superdirt"],
active_clock=config["active_clock"],
link_clock=config["link_clock"],
superdirt_config_path=config["superdirt_config_path"],
user_config_path=config["user_config_path"],
deferred_scheduling=config["deferred_scheduling"],
Expand All @@ -80,12 +84,13 @@ def to_dict(self) -> dict:
"midi": self.midi,
"beats": self.beats,
"parameters": self.parameters,
"ppqn": self.ppqn,
"bpm": self.bpm,
"boot_superdirt": self.boot_superdirt,
"superdirt_handler": self.superdirt_handler,
"boot_supercollider": self.boot_supercollider,
"sardine_boot_file": self.sardine_boot_file,
"verbose_superdirt": self.verbose_superdirt,
"superdirt_config_path": self.superdirt_config_path,
"active_clock": self.active_clock,
"link_clock": self.link_clock,
"user_config_path": self.user_config_path,
"deferred_scheduling": self.deferred_scheduling,
}
Expand Down
213 changes: 58 additions & 155 deletions fishery/__main__.py
Original file line number Diff line number Diff line change
@@ -1,158 +1,61 @@
# https://github.com/python/cpython/blob/main/Lib/asyncio/__main__.py
# Taken from the CPython Github Repository. Custom version of the
# asyncio REPL that will autoload Sardine whenever started.

import concurrent.futures
import threading
import platform
import warnings
import inspect
import asyncio
import psutil
import types
import code
import ast
import os

from appdirs import user_data_dir
from asyncio import futures
from pathlib import Path
from rich import print as pretty_print
from rich.panel import Panel

system = platform.system()

# Setting very high priority for this process (time-critical)
warning_text = "[yellow]/!\\\\[/yellow] [red bold] Run Sardine faster by starting it using\
\nadministrator priviledges (sudo on Unix..)[/red bold] [yellow]/!\\\\[/yellow]"
if system == "Windows":
try:
p = psutil.Process(os.getpid())
p.nice(psutil.HIGH_PRIORITY_CLASS)
except psutil.AccessDenied:
pretty_print(Panel.fit(warning_text))
pass
else:
try:
p = psutil.Process(os.getpid())
p.nice(-20)
except psutil.AccessDenied:
pretty_print(Panel.fit(warning_text))
pass


# Appdirs boilerplate
APP_NAME, APP_AUTHOR = "Sardine", "Bubobubobubo"
USER_DIR = Path(user_data_dir(APP_NAME, APP_AUTHOR))


class AsyncIOInteractiveConsole(code.InteractiveConsole):
def __init__(self, locals, loop):
super().__init__(locals)
self.compile.compiler.flags |= ast.PyCF_ALLOW_TOP_LEVEL_AWAIT

self.loop = loop

def runcode(self, code):
future = concurrent.futures.Future()

def callback():
global repl_future
global repl_future_interrupted

repl_future = None
repl_future_interrupted = False

func = types.FunctionType(code, self.locals)
try:
coro = func()
except SystemExit:
raise
except KeyboardInterrupt as ex:
repl_future_interrupted = True
future.set_exception(ex)
return
except BaseException as ex:
future.set_exception(ex)
return

if not inspect.iscoroutine(coro):
future.set_result(coro)
return

try:
repl_future = self.loop.create_task(coro)
futures._chain_future(repl_future, future)
except BaseException as exc:
future.set_exception(exc)

loop.call_soon_threadsafe(callback)

try:
return future.result()
except SystemExit:
raise
except BaseException:
if repl_future_interrupted:
self.write("\nKeyboardInterrupt\n")
else:
self.showtraceback()


class REPLThread(threading.Thread):
def run(self):
try:
banner = ()
console.push("""import os""")
console.push("""os.environ['SARDINE_INIT_SESSION'] = 'YES'""")
console.push("""from sardine import *""")
console.interact(banner=banner, exitmsg="exiting asyncio REPL...")
finally:
warnings.filterwarnings(
"ignore",
message=r"^coroutine .* was never awaited$",
category=RuntimeWarning,
)

loop.call_soon_threadsafe(loop.stop)
import click

from . import console
from .profiler import Profiler

CONTEXT_SETTINGS = {
"help_option_names": ["-h", "--help"],
}


@click.group(
context_settings=CONTEXT_SETTINGS,
help="Starts sardine in an asyncio REPL.",
invoke_without_command=True,
)
@click.version_option(
package_name="sardine",
prog_name=__package__,
message="%(prog)s for %(package)s v%(version)s",
)
@click.pass_context
def main(ctx: click.Context):
if ctx.invoked_subcommand is None:
console.start()


@main.command(
short_help="Run sardine with a background profiler (requires the yappi package)",
help="""
This command starts the deterministic profiler, yappi, and measures statistics
for both sardine and any functions written in the console. Once the REPL
is closed, a pstats file will be written containing the session's stats.
You can inspect the file's contents with Python's built-in pstats module
or a third-party package like snakeviz.
""",
)
@click.option(
"-c",
"--clock",
default="wall",
help="The clock type to use. Wall time includes time spent waiting, "
"while CPU time ignores it.",
show_default=True,
type=click.Choice(("cpu", "wall"), case_sensitive=False),
)
@click.option(
"-o",
"filepath",
default="stats.prof",
help="The path to use when outputting the pstats file",
show_default=True,
type=click.Path(dir_okay=False, writable=True),
)
def profile(clock: str, filepath: str):
profiler = Profiler(clock=clock, filepath=filepath)
with profiler:
console.start()


if __name__ == "__main__":
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)

repl_locals = {"asyncio": asyncio}
for key in {
"__name__",
"__package__",
"__loader__",
"__spec__",
"__builtins__",
"__file__",
}:
repl_locals[key] = locals()[key]

console = AsyncIOInteractiveConsole(repl_locals, loop)

repl_future = None
repl_future_interrupted = False

try:
import readline # NoQA
except ImportError:
pass

repl_thread = REPLThread()
repl_thread.daemon = True
repl_thread.start()

while True:
try:
loop.run_forever()
except KeyboardInterrupt:
if repl_future and not repl_future.done():
repl_future.cancel()
repl_future_interrupted = True
continue
else:
break
main()
Loading

0 comments on commit e5191ba

Please sign in to comment.