Basics of libcsound binding.

looper
Ben Niemann 6 years ago
parent 81d808e23c
commit a99eabcad9
  1. 1
      bin/noisicaä
  2. 1
      bin/noisipg
  3. 118
      bin/runtests
  4. 118
      bin/runtests.py
  5. 110
      noisicaa/audioproc/csound.pyx
  6. 14
      noisicaa/audioproc/csound.pyxbld
  7. 22
      noisicaa/audioproc/csound_test.py

@ -2,4 +2,5 @@
LIBDIR=$(readlink -f "$(dirname "$0")/..")
export PYTHONPATH="$LIBDIR:$PYTHONPATH"
export LD_LIBRARY_PATH=${VIRTUAL_ENV}/lib
exec python3 -m noisicaa.editor_main "$@"

@ -2,4 +2,5 @@
LIBDIR=$(readlink -f "$(dirname "$0")/..")
export PYTHONPATH="$LIBDIR:$PYTHONPATH"
export LD_LIBRARY_PATH=${VIRTUAL_ENV}/lib
exec python3 -m noisicaa.audio_playground "$@"

@ -1,116 +1,4 @@
#!/usr/bin/env python3
#!/bin/bash
import argparse
import fnmatch
import logging
import os
import os.path
import sys
import unittest
import coverage
import pyximport
pyximport.install()
LIBDIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
sys.path.insert(0, LIBDIR)
def main(argv):
parser = argparse.ArgumentParser()
parser.add_argument('selectors', type=str, nargs='*')
parser.add_argument('--debug', action='store_true', default=False)
parser.add_argument(
'--log-level',
choices=['debug', 'info', 'warning', 'error', 'critical'],
default='error',
help="Minimum level for log messages written to STDERR.")
parser.add_argument('--nocoverage', action='store_true', default=False)
args = parser.parse_args(argv[1:])
logging.basicConfig()
logging.getLogger().setLevel({
'debug': logging.DEBUG,
'info': logging.INFO,
'warning': logging.WARNING,
'error': logging.ERROR,
'critical': logging.CRITICAL,
}[args.log_level if not args.debug else 'debug'])
loader = unittest.defaultTestLoader
suite = unittest.TestSuite()
if not args.nocoverage:
cov = coverage.Coverage(
source=['noisicaa'],
omit='*_*test.py',
config_file=False)
cov.set_option("run:branch", True)
cov.start()
for dirpath, dirnames, filenames in os.walk(
os.path.join(LIBDIR, 'noisicaa')):
if '__pycache__' in dirnames:
dirnames.remove('__pycache__')
for filename in filenames:
if not fnmatch.fnmatch(filename, '*.py'):
continue
modpath = os.path.join(dirpath, filename)
assert modpath.startswith(LIBDIR + '/')
modpath = modpath[len(LIBDIR)+1:-3]
modname = modpath.replace('/', '.')
logging.info("Loading module %s...", modname)
__import__(modname)
if not fnmatch.fnmatch(filename, '*_*test.py'):
continue
if args.selectors:
matched = False
for selector in args.selectors:
if modname.startswith(selector):
matched = True
if not matched:
continue
modsuite = loader.loadTestsFromName(modname)
suite.addTest(modsuite)
runner = unittest.TextTestRunner(verbosity=2)
result = runner.run(suite)
if not args.nocoverage:
cov.stop()
cov_data = cov.get_data()
total_coverage = cov.html_report(
directory='/tmp/noisicaä.coverage')
file_coverages = []
for path in sorted(cov_data.measured_files()):
_, statements, _, missing, _ = cov.analysis2(path)
try:
file_coverage = 1.0 - 1.0 * len(missing) / len(statements)
except ZeroDivisionError:
file_coverage = 1.0
file_coverages.append(
(os.path.relpath(
path, os.path.abspath(os.path.dirname(__file__))),
file_coverage))
file_coverages = sorted(file_coverages, key=lambda f: f[1])
file_coverages = filter(lambda f: f[1] < 0.8, file_coverages)
file_coverages = list(file_coverages)
print()
print("Total coverage: %.1f%%" % total_coverage)
for path, file_coverage in file_coverages[:5]:
print("% 3.1f%% %s" % (100 * file_coverage, path))
print("Coverage report: file:///tmp/noisicaä.coverage/index.html")
print()
return 0 if result.wasSuccessful() else 1
if __name__ == '__main__':
sys.exit(main(sys.argv))
export LD_LIBRARY_PATH=${VIRTUAL_ENV}/lib
exec python3 $(readlink -f "$(dirname "$0")/runtests.py") "$@"

@ -0,0 +1,118 @@
#!/usr/bin/env python3
import argparse
import fnmatch
import logging
import os
import os.path
import sys
import unittest
import coverage
import pyximport
pyximport.install()
LIBDIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
sys.path.insert(0, LIBDIR)
os.environ['LD_LIBRARY_PATH'] = os.path.join(os.getenv('VIRTUAL_ENV'), 'lib')
def main(argv):
parser = argparse.ArgumentParser()
parser.add_argument('selectors', type=str, nargs='*')
parser.add_argument('--debug', action='store_true', default=False)
parser.add_argument(
'--log-level',
choices=['debug', 'info', 'warning', 'error', 'critical'],
default='error',
help="Minimum level for log messages written to STDERR.")
parser.add_argument('--nocoverage', action='store_true', default=False)
args = parser.parse_args(argv[1:])
logging.basicConfig()
logging.getLogger().setLevel({
'debug': logging.DEBUG,
'info': logging.INFO,
'warning': logging.WARNING,
'error': logging.ERROR,
'critical': logging.CRITICAL,
}[args.log_level if not args.debug else 'debug'])
loader = unittest.defaultTestLoader
suite = unittest.TestSuite()
if not args.nocoverage:
cov = coverage.Coverage(
source=['noisicaa'],
omit='*_*test.py',
config_file=False)
cov.set_option("run:branch", True)
cov.start()
for dirpath, dirnames, filenames in os.walk(
os.path.join(LIBDIR, 'noisicaa')):
if '__pycache__' in dirnames:
dirnames.remove('__pycache__')
for filename in filenames:
if not fnmatch.fnmatch(filename, '*.py'):
continue
modpath = os.path.join(dirpath, filename)
assert modpath.startswith(LIBDIR + '/')
modpath = modpath[len(LIBDIR)+1:-3]
modname = modpath.replace('/', '.')
logging.info("Loading module %s...", modname)
__import__(modname)
if not fnmatch.fnmatch(filename, '*_*test.py'):
continue
if args.selectors:
matched = False
for selector in args.selectors:
if modname.startswith(selector):
matched = True
if not matched:
continue
modsuite = loader.loadTestsFromName(modname)
suite.addTest(modsuite)
runner = unittest.TextTestRunner(verbosity=2)
result = runner.run(suite)
if not args.nocoverage:
cov.stop()
cov_data = cov.get_data()
total_coverage = cov.html_report(
directory='/tmp/noisicaä.coverage')
file_coverages = []
for path in sorted(cov_data.measured_files()):
_, statements, _, missing, _ = cov.analysis2(path)
try:
file_coverage = 1.0 - 1.0 * len(missing) / len(statements)
except ZeroDivisionError:
file_coverage = 1.0
file_coverages.append(
(os.path.relpath(
path, os.path.abspath(os.path.dirname(__file__))),
file_coverage))
file_coverages = sorted(file_coverages, key=lambda f: f[1])
file_coverages = filter(lambda f: f[1] < 0.8, file_coverages)
file_coverages = list(file_coverages)
print()
print("Total coverage: %.1f%%" % total_coverage)
for path, file_coverage in file_coverages[:5]:
print("% 3.1f%% %s" % (100 * file_coverage, path))
print("Coverage report: file:///tmp/noisicaä.coverage/index.html")
print()
return 0 if result.wasSuccessful() else 1
if __name__ == '__main__':
sys.exit(main(sys.argv))

@ -0,0 +1,110 @@
import logging
import os
from libc cimport stdio
from libc cimport string
### DECLARATIONS ##########################################################
cdef extern from "stdarg.h" nogil:
ctypedef int va_list
cdef extern from "stdio.h" nogil:
int vsnprintf(char *str, size_t size, const char *format, va_list ap)
cdef extern from "csound/csound.h" nogil:
ctypedef int CSOUND;
int csoundGetVersion()
void csoundSetDefaultMessageCallback(
void (*csoundMessageCallback_)(CSOUND*,
int attr,
const char* format,
va_list))
void csoundInitialize(int)
void* csoundCreate(void_p)
void csoundDestroy(void*)
### LOGGING ###############################################################
# standard message
CSOUNDMSG_DEFAULT = 0x0000
# error message (initerror, perferror, etc.)
CSOUNDMSG_ERROR = 0x1000
# orchestra opcodes (e.g. printks)
CSOUNDMSG_ORCH = 0x2000
# for progress display and heartbeat characters
CSOUNDMSG_REALTIME = 0x3000
# warning messages
CSOUNDMSG_WARNING = 0x4000
CSOUNDMSG_TYPE_MASK = 0x7000
_logger = logging.getLogger('csound')
_logbuf = bytearray()
cdef void _message_callback(
CSOUND* csnd, int attr, const char* fmt, va_list args) nogil:
cdef char buf[10240]
cdef int l
l = vsnprintf(buf, sizeof(buf), fmt, args)
with gil:
try:
_logbuf.extend(buf[:l])
while True:
eol = _logbuf.find(b'\n')
if eol == -1:
break
line = _logbuf[0:eol]
del _logbuf[0:eol+1]
line = line.decode('utf-8', 'replace')
line = line.expandtabs(tabsize=8)
if attr & CSOUNDMSG_TYPE_MASK == CSOUNDMSG_ERROR:
_logger.error('%s', line)
elif attr & CSOUNDMSG_TYPE_MASK == CSOUNDMSG_WARNING:
_logger.warning('%s', line)
else:
_logger.info('%s', line)
except Exception as exc:
_logger.exception("Exception in csound message callback:")
os._exit(1)
csoundSetDefaultMessageCallback(_message_callback)
### GLOBAL INIT ###########################################################
CSOUNDINIT_NO_SIGNAL_HANDLER = 1
CSOUNDINIT_NO_ATEXIT = 2
csoundInitialize(CSOUNDINIT_NO_SIGNAL_HANDLER | CSOUNDINIT_NO_ATEXIT)
### CLIENT CODE ###########################################################
__version__ = '%d.%02d.%d' % (
csoundGetVersion() // 1000,
(csoundGetVersion() // 10) % 100,
csoundGetVersion() % 10)
cdef class CSound(object):
cdef void* csnd
def __cinit__(self):
self.csnd = csoundCreate(None)
def __dealloc__(self):
if self.csnd != NULL:
self.close()
def close(self):
assert self.csnd != NULL
csoundDestroy(self.csnd)
self.csnd = NULL

@ -0,0 +1,14 @@
# -*- mode: python -*-
import os
import os.path
def make_ext(modname, pyxfilename):
from distutils.extension import Extension
return Extension(
name = modname,
sources=[pyxfilename],
libraries=['csound64'],
library_dirs=[os.path.join(os.getenv('VIRTUAL_ENV'), 'lib')],
include_dirs=[os.path.join(os.getenv('VIRTUAL_ENV'), 'include')],
)

@ -0,0 +1,22 @@
#!/usr/bin/python3
import unittest
if __name__ == '__main__':
import pyximport
pyximport.install()
from . import csound
class CSoundTest(unittest.TestCase):
def test_version(self):
self.assertEqual(csound.__version__, '6.07.0')
def test_constructor(self):
csnd = csound.CSound()
csnd.close()
if __name__ == '__main__':
unittest.main()
Loading…
Cancel
Save