- Run all audio in a single process. - Instead of a main audio process connected to the backend and one process per opened project. - Audio engine is composed of a tree of 'realms', with the top-level realm hooked up to the backend, and each project creates a sub-realm. - Run plugins in a separate process, isolated from the main audio engine. - Turns out I really want to support the LV2 instance access feature, so UIs need to run in the same process as the plugin itself. - So plugins need to be isolated from each other, so they can use different UI toolkits without linker symbol clashes. I.e. they can't all run in the main audio process. - Separate audio processing from the project process. - Instead of generating MIDI events from the project and route them to the audio pipeline, have processors in the pipeline, which capture the full state of the project and emit events during playback. Changes to the project update those processors to keep them in sync. - Use protocol buffers for a lot of internal messages in non-realtime contexts. - Very basic support for LV2 plugin UIs. - Trying to get that working triggered all those changes above... - And many, many more changes...looper
parent
66fb6afb0d
commit
45be492aed
@ -0,0 +1,184 @@
|
||||
# @begin:license
|
||||
#
|
||||
# Copyright (c) 2015-2018, Benjamin Niemann <pink@odahoda.de>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
#
|
||||
# @end:license
|
||||
|
||||
from distutils import core
|
||||
from distutils.command.build import build
|
||||
from distutils.command.install import install
|
||||
import urllib.request
|
||||
import os
|
||||
import os.path
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import tarfile
|
||||
|
||||
VERSION = '0.10.0'
|
||||
FILENAME = 'suil-%s.tar.bz2' % VERSION
|
||||
DOWNLOAD_URL = 'http://git.drobilla.net/cgit.cgi/suil.git/snapshot/%s' % FILENAME
|
||||
|
||||
assert os.getenv('VIRTUAL_ENV'), "Not running in a virtualenv."
|
||||
|
||||
|
||||
class SuilMixin(object):
|
||||
user_options = [
|
||||
('build-base=', 'b',
|
||||
"base directory for build library"),
|
||||
]
|
||||
|
||||
def initialize_options(self):
|
||||
self.build_base = os.path.join(os.getenv('VIRTUAL_ENV'), 'build', 'suil')
|
||||
|
||||
def finalize_options(self):
|
||||
pass
|
||||
|
||||
@property
|
||||
def archive_path(self):
|
||||
return os.path.join(self.build_base, FILENAME)
|
||||
|
||||
@property
|
||||
def src_dir(self):
|
||||
return os.path.join(self.build_base, 'src')
|
||||
|
||||
|
||||
class BuildSuil(SuilMixin, core.Command):
|
||||
def run(self):
|
||||
if not os.path.isdir(self.build_base):
|
||||
os.makedirs(self.build_base)
|
||||
|
||||
self._download_archive(self.archive_path)
|
||||
self._unpack_archive(self.archive_path, self.src_dir)
|
||||
self._configure(self.src_dir)
|
||||
self._build(self.src_dir)
|
||||
|
||||
def _download_archive(self, archive_path):
|
||||
if os.path.exists(archive_path):
|
||||
return
|
||||
|
||||
total_bytes = 0
|
||||
with urllib.request.urlopen(DOWNLOAD_URL) as fp_in:
|
||||
with open(archive_path + '.partial', 'wb') as fp_out:
|
||||
last_report = time.time()
|
||||
try:
|
||||
while True:
|
||||
dat = fp_in.read(10240)
|
||||
if not dat:
|
||||
break
|
||||
fp_out.write(dat)
|
||||
total_bytes += len(dat)
|
||||
if time.time() - last_report > 1:
|
||||
sys.stderr.write(
|
||||
'Downloading %s: %d bytes\r'
|
||||
% (DOWNLOAD_URL, total_bytes))
|
||||
sys.stderr.flush()
|
||||
last_report = time.time()
|
||||
finally:
|
||||
sys.stderr.write('\033[K')
|
||||
sys.stderr.flush()
|
||||
|
||||
os.rename(archive_path + '.partial', archive_path)
|
||||
print('Downloaded %s: %d bytes' % (DOWNLOAD_URL, total_bytes))
|
||||
|
||||
def _unpack_archive(self, archive_path, src_dir):
|
||||
if os.path.isdir(src_dir):
|
||||
return
|
||||
|
||||
print("Extracting...")
|
||||
|
||||
base_dir = None
|
||||
with tarfile.open(archive_path, 'r:bz2') as fp:
|
||||
for path in fp.getnames():
|
||||
while path:
|
||||
path, b = os.path.split(path)
|
||||
if not path:
|
||||
if base_dir is None:
|
||||
base_dir = b
|
||||
elif b != base_dir:
|
||||
raise RuntimeError(
|
||||
"No common base dir (%s)" % b)
|
||||
|
||||
fp.extractall(self.build_base)
|
||||
|
||||
os.rename(os.path.join(self.build_base, base_dir), src_dir)
|
||||
print("Extracted to %s" % src_dir)
|
||||
return src_dir
|
||||
|
||||
def _configure(self, src_dir):
|
||||
if os.path.exists(os.path.join(src_dir, '.configure.complete')):
|
||||
return
|
||||
|
||||
print("Running waf configure...")
|
||||
subprocess.run(
|
||||
['./waf',
|
||||
'configure',
|
||||
'--prefix=%s' % os.getenv('VIRTUAL_ENV'),
|
||||
],
|
||||
cwd=src_dir,
|
||||
env=dict(
|
||||
os.environ,
|
||||
PKG_CONFIG_PATH=os.path.join(os.getenv('VIRTUAL_ENV'), 'lib', 'pkgconfig')),
|
||||
check=True)
|
||||
open(os.path.join(src_dir, '.configure.complete'), 'w').close()
|
||||
|
||||
def _build(self, src_dir):
|
||||
if os.path.exists(os.path.join(src_dir, '.build.complete')):
|
||||
return
|
||||
|
||||
print("Running waf build...")
|
||||
subprocess.run(
|
||||
['./waf', 'build'],
|
||||
cwd=src_dir,
|
||||
check=True)
|
||||
open(os.path.join(src_dir, '.build.complete'), 'w').close()
|
||||
|
||||
|
||||
class InstallSuil(SuilMixin, core.Command):
|
||||
@property
|
||||
def sentinel_path(self):
|
||||
return os.path.join(
|
||||
os.getenv('VIRTUAL_ENV'), '.suil-%s-installed' % VERSION)
|
||||
|
||||
def run(self):
|
||||
if os.path.exists(self.sentinel_path):
|
||||
return
|
||||
|
||||
print("Running waf install...")
|
||||
subprocess.run(
|
||||
['./waf', 'install'],
|
||||
cwd=self.src_dir,
|
||||
check=True)
|
||||
open(self.sentinel_path, 'w').close()
|
||||
|
||||
def get_outputs(self):
|
||||
return [self.sentinel_path]
|
||||
|
||||
|
||||
build.sub_commands.append(('build_suil', None))
|
||||
install.sub_commands.insert(0, ('install_suil', None))
|
||||
|
||||
|
||||
core.setup(
|
||||
name = 'suil',
|
||||
version = VERSION,
|
||||
cmdclass = {
|
||||
'build_suil': BuildSuil,
|
||||
'install_suil': InstallSuil,
|
||||
},
|
||||
requires=['lv2'],
|
||||
)
|
@ -0,0 +1,93 @@
|
||||
<CsoundSynthesizer>
|
||||
<CsLicense>
|
||||
@begin:license
|
||||
|
||||
Copyright (c) 2015-2018, Benjamin Niemann <pink@odahoda.de>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
@end:license
|
||||
|
||||
Based on http://www.adp-gmbh.ch/csound/instruments/gong.html
|
||||
</CsLicense>
|
||||
<CsOptions>
|
||||
-d
|
||||
</CsOptions>
|
||||
<CsInstruments>
|
||||
sr = 44100
|
||||
kr = 441
|
||||
ksmps= 100
|
||||
nchnls = 2
|
||||
|
||||
instr 1; *****************************************************************
|
||||
ilen = p3
|
||||
ifrq = cpspch(p4)
|
||||
iamp = p5
|
||||
ipan = p6
|
||||
|
||||
ifrq1 = 1.0000 * ifrq
|
||||
ifrq2 = 1.1541 * ifrq
|
||||
ifrq3 = 1.6041 * ifrq
|
||||
ifrq4 = 1.5208 * ifrq
|
||||
ifrq5 = 1.4166 * ifrq
|
||||
ifrq6 = 2.7916 * ifrq
|
||||
ifrq7 = 3.3833 * ifrq
|
||||
|
||||
iamp1 = 1.0000 * iamp
|
||||
iamp2 = 0.8333 * iamp
|
||||
iamp3 = 0.6667 * iamp
|
||||
iamp4 = 1.0000 * iamp
|
||||
iamp5 = 0.3333 * iamp
|
||||
iamp6 = 0.3333 * iamp
|
||||
iamp7 = 0.3333 * iamp
|
||||
|
||||
aenv1 oscili iamp1, 1/ilen, 2
|
||||
aenv2 oscili iamp2, 1/ilen, 2
|
||||
aenv3 oscili iamp3, 1/ilen, 2
|
||||
aenv4 oscili iamp4, 1/ilen, 2
|
||||
aenv5 oscili iamp5, 1/ilen, 2
|
||||
aenv6 oscili iamp6, 1/ilen, 2
|
||||
aenv7 oscili iamp7, 1/ilen, 2
|
||||
|
||||
asig1 oscili aenv1, ifrq1, 1
|
||||
asig2 oscili aenv2, ifrq2, 1
|
||||
asig3 oscili aenv3, ifrq3, 1
|
||||
asig4 oscili aenv4, ifrq4, 1
|
||||
asig5 oscili aenv5, ifrq5, 1
|
||||
asig6 oscili aenv6, ifrq6, 1
|
||||
asig7 oscili aenv7, ifrq7, 1
|
||||
|
||||
asig = asig1 + asig2 + asig3 + asig4 + asig5 + asig6 + asig7
|
||||
|
||||
i_sqrt2 = 1.414213562373095
|
||||
i_theta = 3.141592653589793 * 45 * (1 - ipan) / 180
|
||||
asig_l = i_sqrt2 * cos(i_theta) * asig
|
||||
asig_r = i_sqrt2 * sin(i_theta) * asig
|
||||
|
||||
out asig_l, asig_r
|
||||
endin
|
||||
</CsInstruments>
|
||||
<CsScore>
|
||||
f1 0 512 9 1 1 0 ; basic waveform
|
||||
f2 0 513 5 128 512 1 ; envelopes
|
||||
|
||||
i1 0 2 7.02 5000 -1
|
||||
i1 0 3 6.02 5000 0
|
||||
i1 0.3 2 7.06 4000 1
|
||||
i1 0.3 3 6.06 4000 0
|
||||
i1 0.6 2 7.04 3500 0
|
||||
i1 0.6 3 6.04 3500 0
|
||||
</CsScore>
|
||||
</CsoundSynthesizer>
|