15 Commits

Author SHA1 Message Date
Bartłomiej Pluta
24bcb25bee Enable tuning 2019-09-06 22:59:09 +02:00
Bartłomiej Pluta
83ef3f6888 Create CLI scaffolding 2019-09-06 18:26:02 +02:00
Bartłomiej Pluta
3061dae723 Merge branch 'create-virtualenv' 2019-09-06 14:46:51 +02:00
Bartłomiej Pluta
aca227ac5e Create virtual environment with pipenv 2019-09-06 13:48:28 +02:00
Bartłomiej Pluta
7ec967a014 Merge branch 'add-polyphony' 2019-09-06 13:46:02 +02:00
Bartłomiej Pluta
aca6e6bb55 Create tools for compiling waves 2019-07-30 16:51:35 +02:00
Bartłomiej Pluta
a7de7f0279 Optimise time of generating overtones 2019-07-30 15:12:32 +02:00
Bartłomiej Pluta
7e55fe4c1a Add 'plot' function and remove deprecated 'percent' type 2019-07-30 13:59:18 +02:00
Bartłomiej Pluta
8abae7c2ff Add decay to synthetiser 2019-07-29 22:11:30 +02:00
Bartłomiej Pluta
07f08b0557 Add support for integers in passing overtones to synth 2019-07-29 17:59:38 +02:00
Bartłomiej Pluta
13b069dc7d Add support for non-quoted (identifier) map keys being used as string 2019-07-29 17:53:41 +02:00
Bartłomiej Pluta
73ea88d8d9 Overload synth function to accept notes without config object 2019-07-29 17:19:41 +02:00
Bartłomiej Pluta
a5875425fc Enable passing config map to synth function 2019-07-28 21:20:11 +02:00
Bartłomiej Pluta
0dcf5287e1 Add polyphony AND add overtones do synthesed tones 2019-07-28 19:48:39 +02:00
Bartłomiej Pluta
75dcacce67 Merge branch 'add-float-type' 2019-07-28 09:32:31 +02:00
20 changed files with 574 additions and 88 deletions

3
.gitignore vendored
View File

@@ -1,3 +1,4 @@
__pycache__/
*.mus
.idea/*
.idea/*
venv/

15
Pipfile Normal file
View File

@@ -0,0 +1,15 @@
[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true
[dev-packages]
[packages]
sounddevice = "*"
soundfile = "*"
numpy = "*"
matplotlib = "*"
[requires]
python_version = "3.7"

180
Pipfile.lock generated Normal file
View File

@@ -0,0 +1,180 @@
{
"_meta": {
"hash": {
"sha256": "92b0c636f6ac9be9b32aa25b1cdc96dcbe85602d5c68a2084dc39d25392540b6"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.7"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {
"cffi": {
"hashes": [
"sha256:041c81822e9f84b1d9c401182e174996f0bae9991f33725d059b771744290774",
"sha256:046ef9a22f5d3eed06334d01b1e836977eeef500d9b78e9ef693f9380ad0b83d",
"sha256:066bc4c7895c91812eff46f4b1c285220947d4aa46fa0a2651ff85f2afae9c90",
"sha256:066c7ff148ae33040c01058662d6752fd73fbc8e64787229ea8498c7d7f4041b",
"sha256:2444d0c61f03dcd26dbf7600cf64354376ee579acad77aef459e34efcb438c63",
"sha256:300832850b8f7967e278870c5d51e3819b9aad8f0a2c8dbe39ab11f119237f45",
"sha256:34c77afe85b6b9e967bd8154e3855e847b70ca42043db6ad17f26899a3df1b25",
"sha256:46de5fa00f7ac09f020729148ff632819649b3e05a007d286242c4882f7b1dc3",
"sha256:4aa8ee7ba27c472d429b980c51e714a24f47ca296d53f4d7868075b175866f4b",
"sha256:4d0004eb4351e35ed950c14c11e734182591465a33e960a4ab5e8d4f04d72647",
"sha256:4e3d3f31a1e202b0f5a35ba3bc4eb41e2fc2b11c1eff38b362de710bcffb5016",
"sha256:50bec6d35e6b1aaeb17f7c4e2b9374ebf95a8975d57863546fa83e8d31bdb8c4",
"sha256:55cad9a6df1e2a1d62063f79d0881a414a906a6962bc160ac968cc03ed3efcfb",
"sha256:5662ad4e4e84f1eaa8efce5da695c5d2e229c563f9d5ce5b0113f71321bcf753",
"sha256:59b4dc008f98fc6ee2bb4fd7fc786a8d70000d058c2bbe2698275bc53a8d3fa7",
"sha256:73e1ffefe05e4ccd7bcea61af76f36077b914f92b76f95ccf00b0c1b9186f3f9",
"sha256:a1f0fd46eba2d71ce1589f7e50a9e2ffaeb739fb2c11e8192aa2b45d5f6cc41f",
"sha256:a2e85dc204556657661051ff4bab75a84e968669765c8a2cd425918699c3d0e8",
"sha256:a5457d47dfff24882a21492e5815f891c0ca35fefae8aa742c6c263dac16ef1f",
"sha256:a8dccd61d52a8dae4a825cdbb7735da530179fea472903eb871a5513b5abbfdc",
"sha256:ae61af521ed676cf16ae94f30fe202781a38d7178b6b4ab622e4eec8cefaff42",
"sha256:b012a5edb48288f77a63dba0840c92d0504aa215612da4541b7b42d849bc83a3",
"sha256:d2c5cfa536227f57f97c92ac30c8109688ace8fa4ac086d19d0af47d134e2909",
"sha256:d42b5796e20aacc9d15e66befb7a345454eef794fdb0737d1af593447c6c8f45",
"sha256:dee54f5d30d775f525894d67b1495625dd9322945e7fee00731952e0368ff42d",
"sha256:e070535507bd6aa07124258171be2ee8dfc19119c28ca94c9dfb7efd23564512",
"sha256:e1ff2748c84d97b065cc95429814cdba39bcbd77c9c85c89344b317dc0d9cbff",
"sha256:ed851c75d1e0e043cbf5ca9a8e1b13c4c90f3fbd863dacb01c0808e2b5204201"
],
"version": "==1.12.3"
},
"cycler": {
"hashes": [
"sha256:1d8a5ae1ff6c5cf9b93e8811e581232ad8920aeec647c37316ceac982b08cb2d",
"sha256:cd7b2d1018258d7247a71425e9f26463dfb444d411c39569972f4ce586b0c9d8"
],
"version": "==0.10.0"
},
"kiwisolver": {
"hashes": [
"sha256:05b5b061e09f60f56244adc885c4a7867da25ca387376b02c1efc29cc16bcd0f",
"sha256:26f4fbd6f5e1dabff70a9ba0d2c4bd30761086454aa30dddc5b52764ee4852b7",
"sha256:3b2378ad387f49cbb328205bda569b9f87288d6bc1bf4cd683c34523a2341efe",
"sha256:400599c0fe58d21522cae0e8b22318e09d9729451b17ee61ba8e1e7c0346565c",
"sha256:47b8cb81a7d18dbaf4fed6a61c3cecdb5adec7b4ac292bddb0d016d57e8507d5",
"sha256:53eaed412477c836e1b9522c19858a8557d6e595077830146182225613b11a75",
"sha256:58e626e1f7dfbb620d08d457325a4cdac65d1809680009f46bf41eaf74ad0187",
"sha256:5a52e1b006bfa5be04fe4debbcdd2688432a9af4b207a3f429c74ad625022641",
"sha256:5c7ca4e449ac9f99b3b9d4693debb1d6d237d1542dd6a56b3305fe8a9620f883",
"sha256:682e54f0ce8f45981878756d7203fd01e188cc6c8b2c5e2cf03675390b4534d5",
"sha256:79bfb2f0bd7cbf9ea256612c9523367e5ec51d7cd616ae20ca2c90f575d839a2",
"sha256:7f4dd50874177d2bb060d74769210f3bce1af87a8c7cf5b37d032ebf94f0aca3",
"sha256:8944a16020c07b682df861207b7e0efcd2f46c7488619cb55f65882279119389",
"sha256:8aa7009437640beb2768bfd06da049bad0df85f47ff18426261acecd1cf00897",
"sha256:939f36f21a8c571686eb491acfffa9c7f1ac345087281b412d63ea39ca14ec4a",
"sha256:9733b7f64bd9f807832d673355f79703f81f0b3e52bfce420fc00d8cb28c6a6c",
"sha256:a02f6c3e229d0b7220bd74600e9351e18bc0c361b05f29adae0d10599ae0e326",
"sha256:a0c0a9f06872330d0dd31b45607197caab3c22777600e88031bfe66799e70bb0",
"sha256:acc4df99308111585121db217681f1ce0eecb48d3a828a2f9bbf9773f4937e9e",
"sha256:b64916959e4ae0ac78af7c3e8cef4becee0c0e9694ad477b4c6b3a536de6a544",
"sha256:d3fcf0819dc3fea58be1fd1ca390851bdb719a549850e708ed858503ff25d995",
"sha256:d52e3b1868a4e8fd18b5cb15055c76820df514e26aa84cc02f593d99fef6707f",
"sha256:db1a5d3cc4ae943d674718d6c47d2d82488ddd94b93b9e12d24aabdbfe48caee",
"sha256:e3a21a720791712ed721c7b95d433e036134de6f18c77dbe96119eaf7aa08004",
"sha256:e8bf074363ce2babeb4764d94f8e65efd22e6a7c74860a4f05a6947afc020ff2",
"sha256:f16814a4a96dc04bf1da7d53ee8d5b1d6decfc1a92a63349bb15d37b6a263dd9",
"sha256:f2b22153870ca5cf2ab9c940d7bc38e8e9089fa0f7e5856ea195e1cf4ff43d5a",
"sha256:f790f8b3dff3d53453de6a7b7ddd173d2e020fb160baff578d578065b108a05f"
],
"version": "==1.1.0"
},
"matplotlib": {
"hashes": [
"sha256:1febd22afe1489b13c6749ea059d392c03261b2950d1d45c17e3aed812080c93",
"sha256:31a30d03f39528c79f3a592857be62a08595dec4ac034978ecd0f814fa0eec2d",
"sha256:4442ce720907f67a79d45de9ada47be81ce17e6c2f448b3c64765af93f6829c9",
"sha256:796edbd1182cbffa7e1e7a97f1e141f875a8501ba8dd834269ae3cd45a8c976f",
"sha256:934e6243df7165aad097572abf5b6003c77c9b6c480c3c4de6f2ef1b5fdd4ec0",
"sha256:bab9d848dbf1517bc58d1f486772e99919b19efef5dd8596d4b26f9f5ee08b6b",
"sha256:c1fe1e6cdaa53f11f088b7470c2056c0df7d80ee4858dadf6cbe433fcba4323b",
"sha256:e5b8aeca9276a3a988caebe9f08366ed519fff98f77c6df5b64d7603d0e42e36",
"sha256:ec6bd0a6a58df3628ff269978f4a4b924a0d371ad8ce1f8e2b635b99e482877a"
],
"index": "pypi",
"version": "==3.1.1"
},
"numpy": {
"hashes": [
"sha256:03f2ebcbffcce2dec8860633b89a93e80c6a239d21a77ae8b241450dc21e8c35",
"sha256:078c8025da5ab9e8657edc9c2a1e9642e06e953bc7baa2e65c1aa9d9dfb7e98b",
"sha256:0fbfa98c5d5c3c6489cc1e852ec94395d51f35d9ebe70c6850e47f465038cdf4",
"sha256:1c841033f4fe6801648180c3033c45b3235a8bbd09bc7249010f99ea27bb6790",
"sha256:2c0984a01ddd0aeec89f0ce46ef21d64761048cd76c0074d0658c91f9131f154",
"sha256:4c166dcb0fff7cb3c0bbc682dfb5061852a2547efb6222e043a7932828c08fb5",
"sha256:8c2d98d0623bd63fb883b65256c00454d5f53127a5a7bcdaa8bdc582814e8cb4",
"sha256:8cb4b6ae45aad6d26712a1ce0a3f2556c5e1484867f9649e03496e45d6a5eba4",
"sha256:93050e73c446c82065b7410221b07682e475ac51887cd9368227a5d944afae80",
"sha256:a3f6b3024f8826d8b1490e6e2a9b99e841cd2c375791b1df62991bd8f4c00b89",
"sha256:bede70fd8699695363f39e86c1e869b2c8b74fb5ef135a67b9e1eeebff50322a",
"sha256:c304b2221f33489cd15a915237a84cdfe9420d7e4d4828c78a0820f9d990395c",
"sha256:f11331530f0eff69a758d62c2461cd98cdc2eae0147279d8fc86e0464eb7e8ca",
"sha256:fa5f2a8ef1e07ba258dc07d4dd246de23ef4ab920ae0f3fa2a1cc5e90f0f1888",
"sha256:fb6178b0488b0ce6a54bc4accbdf5225e937383586555604155d64773f6beb2b",
"sha256:fd5e830d4dc31658d61a6452cd3e842213594d8c15578cdae6829e36ad9c0930"
],
"index": "pypi",
"version": "==1.17.1"
},
"pycparser": {
"hashes": [
"sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3"
],
"version": "==2.19"
},
"pyparsing": {
"hashes": [
"sha256:6f98a7b9397e206d78cc01df10131398f1c8b8510a2f4d97d9abd82e1aacdd80",
"sha256:d9338df12903bbf5d65a0e4e87c2161968b10d2e489652bb47001d82a9b028b4"
],
"version": "==2.4.2"
},
"python-dateutil": {
"hashes": [
"sha256:7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb",
"sha256:c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e"
],
"version": "==2.8.0"
},
"six": {
"hashes": [
"sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c",
"sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"
],
"version": "==1.12.0"
},
"sounddevice": {
"hashes": [
"sha256:8d0571349f9a438a97f2c69da760f195cf5ddf2351072199cc1dfede4785a207",
"sha256:a0dda9dea9a5038ec0dafee4cdebd6d9a09086405d4833a7e3f6b18c109f89f3",
"sha256:a71cd3daabc8cb13e0ec9a29e7d4ba9b707386f3c81b3501b20f64209bcaece7",
"sha256:b038315e3497e01b1cb22630bc9b5025fcfad40e5ef7c125a9d565befa7220e0",
"sha256:c340d0a68945439571a13b287bd11b2d2884b61cd47cc5de8356d88221edf580"
],
"index": "pypi",
"version": "==0.3.13"
},
"soundfile": {
"hashes": [
"sha256:181393f8daac38bda997c4284b760bcd726dc0e847f620020d7bf2d3d538a985",
"sha256:3f90971bb1884575ada9a0731e878c91cc1ffde14a778f0857deaa2fd04849ac",
"sha256:637f6218c867b8cae80f6989634a0813b416b3e6132480d056e6e5a89a921571",
"sha256:7fe04272cbfb94ab648acb0db31ace0800f5a4cf46e9d45b4d71ddb15abce1a0",
"sha256:80a3d2ed37199ffa7066c097d7b82b8413922e22870b62b54dc264bf0169f4c2"
],
"index": "pypi",
"version": "==0.10.2"
}
},
"develop": {}
}

2
os_requirements.txt Normal file
View File

@@ -0,0 +1,2 @@
You have to have installed `PortAudio` in order to use app.

View File

@@ -1,4 +1,5 @@
from smnp.ast.node.atom import LiteralParser
from smnp.ast.node.identifier import IdentifierLiteralParser
from smnp.ast.node.iterable import abstractIterableParser
from smnp.ast.node.model import Node
from smnp.ast.node.operator import BinaryOperator, Operator
@@ -31,12 +32,15 @@ class Map(Node):
def MapParser(input):
from smnp.ast.node.expression import ExpressionParser
keyParser = LiteralParser
keyParser = Parser.oneOf(
LiteralParser,
IdentifierLiteralParser
)
valueParser = ExpressionParser
mapEntryParser = Parser.allOf(
keyParser,
Parser.terminal(TokenType.ARROW, createNode=Operator.withValue, doAssert=True),
Parser.terminal(TokenType.ARROW, createNode=Operator.withValue),
Parser.doAssert(valueParser, "expression"),
createNode=MapEntry.withValues
)

0
smnp/cli/__init__.py Normal file
View File

23
smnp/cli/parser.py Normal file
View File

@@ -0,0 +1,23 @@
import argparse
VERSION = "0.1"
DESCRIPTION = """
Simple Music Notation Processor is a command line tool enabling you to do some music stuff using custom domain-specific language.
"""
class CliParser(object):
def __init__(self):
self.parser = argparse.ArgumentParser(description=DESCRIPTION)
self.parser.add_argument('file', nargs='*', help='a file containing SMNP code')
self.parser.add_argument('-c', '--code', action='append', default=[], type=str, help='a string with SMNP code')
self.parser.add_argument('-m', '--mic', action='store_true', help='test microphone level')
self.parser.add_argument('-C', '--config', type=argparse.FileType('-r'), help='a file containing settings (not implemented yet)')
self.parser.add_argument('-p', '--params', action='append', help='pass arguments to program (not implemented yet)')
self.parser.add_argument('-v', '--version', action='version')
self.parser.add_argument('--tokens', action='store_true', help='print tokens of parsed code')
self.parser.add_argument('--ast', action='store_true', help='print abstract syntax tree of parsed code')
self.parser.add_argument('--dry-run', action='store_true', help='don\'t execute passed code')
self.parser.version = VERSION
def parse(self):
return self.parser.parse_args()

View File

@@ -1,17 +1,38 @@
import sys
from smnp.cli.parser import CliParser
from smnp.error.base import SmnpException
from smnp.library.loader import loadStandardLibrary
from smnp.module.mic.lib.detector.noise import NoiseDetector
from smnp.program.interpreter import Interpreter
def interpretFile(args, file):
stdLibraryEnv = loadStandardLibrary() if not args.dry_run else None
Interpreter.interpretFile(file, printTokens=args.tokens, printAst=args.ast, execute=not args.dry_run, baseEnvironment=stdLibraryEnv)
def interpretString(args, string):
stdLibraryEnv = loadStandardLibrary() if not args.dry_run else None
Interpreter.interpretString(string, printTokens=args.tokens, printAst=args.ast, execute=not args.dry_run, baseEnvironment=stdLibraryEnv, source='<cli>')
def main():
try:
stdLibraryEnv = loadStandardLibrary()
Interpreter.interpretFile(sys.argv[1], printTokens=False, printAst=False, execute=True, baseEnvironment=stdLibraryEnv)
parser = CliParser()
args = parser.parse()
if args.mic:
nd = NoiseDetector()
nd.test()
for code in args.code:
interpretString(args, code)
for file in args.file:
interpretFile(args, file)
except SmnpException as e:
print(e.message())
except KeyboardInterrupt:
print("Program interrupted")

View File

@@ -1,4 +1,4 @@
from smnp.module.synth.function import synth, pause
from smnp.module.synth.function import synth, pause, plot, compile
functions = [ synth.function, pause.function ]
functions = [ synth.function, pause.function, plot.function, compile.function ]
methods = []

View File

@@ -0,0 +1,157 @@
from smnp.error.runtime import RuntimeException
from smnp.function.model import Function, CombinedFunction
from smnp.function.signature import varargSignature
from smnp.module.synth.lib.wave import compilePolyphony
from smnp.type.model import Type
from smnp.type.signature.matcher.list import listOf
from smnp.type.signature.matcher.type import ofTypes, ofType
DEFAULT_BPM = 120
DEFAULT_OVERTONES = [0.4, 0.3, 0.1, 0.1, 0.1]
DEFAULT_DECAY = 4
DEFAULT_ATTACK = 100
DEFAULT_TUNING = 440
def getBpm(config):
key = Type.string("bpm")
if key in config.value:
bpm = config.value[key]
if bpm.type != Type.INTEGER or bpm.value <= 0:
raise RuntimeException("The 'bpm' property must be positive integer", None)
return bpm.value
return DEFAULT_BPM
def getOvertones(config):
key = Type.string("overtones")
if key in config.value:
overtones = config.value[key]
rawOvertones = [overtone.value for overtone in overtones.value]
if overtones.type != Type.LIST or not all(overtone.type in [Type.FLOAT, Type.INTEGER] for overtone in overtones.value):
raise RuntimeException("The 'overtones' property must be list of floats", None)
if len(rawOvertones) < 1:
raise RuntimeException("The 'overtones' property must contain one overtone at least", None)
if any(overtone < 0 for overtone in rawOvertones):
raise RuntimeException("The 'overtones' property mustn't contain negative values", None)
if sum(rawOvertones) > 1.0:
raise RuntimeException("The 'overtones' property must contain overtones which sum is not greater than 1.0", None)
return rawOvertones
return DEFAULT_OVERTONES
def getDecay(config):
key = Type.string("decay")
if key in config.value:
decay = config.value[key]
if not decay.type in [Type.INTEGER, Type.FLOAT] or decay.value < 0:
raise RuntimeException("The 'decay' property must be non-negative integer or float", None)
return decay.value
return DEFAULT_DECAY
def getAttack(config):
key = Type.string("attack")
if key in config.value:
attack = config.value[key]
if not attack.type in [Type.INTEGER, Type.FLOAT] or attack.value < 0:
raise RuntimeException("The 'attack' property must be non-negative integer or float", None)
return attack.value
return DEFAULT_ATTACK
def getTuning(config):
key = Type.string("tuning")
if key in config.value:
tuning = config.value[key]
if not tuning.type in [Type.INTEGER, Type.FLOAT] or tuning.value < 0:
raise RuntimeException("The 'tuning' property must be non-negative integer or float", None)
return tuning.value
return DEFAULT_TUNING
class Config:
def __init__(self, bpm, overtones, decay, attack, tuning):
self.bpm = bpm
self.overtones = overtones
self.decay = decay
self.attack = attack
self.tuning = tuning
@staticmethod
def default():
return Config(DEFAULT_BPM, DEFAULT_OVERTONES, DEFAULT_DECAY, DEFAULT_ATTACK, DEFAULT_TUNING)
_signature1 = varargSignature(listOf(Type.NOTE, Type.INTEGER))
def _function1(env, notes):
return Type.list([Type.float(float(m)) for m in __function1(notes)])
def __function1(notes):
return compilePolyphony([note.value for note in notes], Config.default())
_signature2 = varargSignature(ofTypes(Type.NOTE, Type.INTEGER))
def _function2(env, notes):
return Type.list([Type.float(float(m)) for m in __function2(notes)])
def __function2(notes):
return compilePolyphony([ notes ], Config.default())
_signature3 = varargSignature(listOf(Type.NOTE, Type.INTEGER), ofType(Type.MAP))
def _function3(env, config, notes):
return Type.list([ Type.float(float(m)) for m in __function3(config, notes) ])
def __function3(config, notes):
rawNotes = [note.value for note in notes]
bpm = getBpm(config)
overtones = getOvertones(config)
decay = getDecay(config)
attack = getAttack(config)
tuning = getTuning(config)
return compilePolyphony(rawNotes, Config(bpm, overtones, decay, attack, tuning))
_signature4 = varargSignature(ofTypes(Type.NOTE, Type.INTEGER), ofType(Type.MAP))
def _function4(env, config, notes):
return Type.list([ Type.float(float(m)) for m in __function4(config, notes) ])
def __function4(config, notes):
bpm = getBpm(config)
overtones = getOvertones(config)
decay = getDecay(config)
attack = getAttack(config)
tuning = getTuning(config)
return compilePolyphony([ notes ], Config(bpm, overtones, decay, attack, tuning))
function = CombinedFunction(
'wave',
Function(_signature1, _function1),
Function(_signature2, _function2),
Function(_signature3, _function3),
Function(_signature4, _function4),
)

View File

@@ -1,13 +1,13 @@
from smnp.function.model import Function
from smnp.function.signature import signature
from smnp.module.synth.lib import player
from smnp.module.synth.lib.wave import pause
from smnp.type.model import Type
from smnp.type.signature.matcher.type import ofTypes
_signature = signature(ofTypes(Type.INTEGER))
def _function(env, value):
bpm = env.findVariable('bpm')
player.pause(value.value, bpm.value)
pause(value.value, bpm.value)
function = Function(_signature, _function, 'pause')

View File

@@ -0,0 +1,13 @@
from smnp.function.model import Function
from smnp.function.signature import signature
from smnp.module.synth.lib.wave import plot
from smnp.type.model import Type
from smnp.type.signature.matcher.list import listOf
_signature = signature(listOf(Type.FLOAT))
def _function(env, wave):
rawWave = [ m.value for m in wave.value ]
plot(rawWave)
function = Function(_signature, _function, 'plotWave')

View File

@@ -1,13 +1,47 @@
from smnp.function.model import Function
from smnp.function.signature import signature
from smnp.module.synth.lib.player import play
from smnp.function.model import Function, CombinedFunction
from smnp.function.signature import varargSignature
from smnp.module.synth.function import compile
from smnp.module.synth.lib.wave import play
from smnp.type.model import Type
from smnp.type.signature.matcher.type import ofType
from smnp.type.signature.matcher.list import listOf
from smnp.type.signature.matcher.type import ofTypes, ofType
_signature = signature(ofType(Type.NOTE))
def _function(env, note):
bpm = env.findVariable('bpm')
play(note.value, bpm.value)
_signature1 = varargSignature(listOf(Type.NOTE, Type.INTEGER))
def _function1(env, notes):
wave = compile.__function1(notes)
play(wave)
function = Function(_signature, _function, 'synthNote')
_signature2 = varargSignature(ofTypes(Type.NOTE, Type.INTEGER))
def _function2(env, notes):
wave = compile.__function2(notes)
play(wave)
_signature3 = varargSignature(listOf(Type.NOTE, Type.INTEGER), ofType(Type.MAP))
def _function3(env, config, notes):
wave = compile.__function3(config, notes)
play(wave)
_signature4 = varargSignature(ofTypes(Type.NOTE, Type.INTEGER), ofType(Type.MAP))
def _function4(env, config, notes):
wave = compile.__function4(config, notes)
play(wave)
_signature5 = varargSignature(listOf(Type.FLOAT))
def _function5(env, waves):
for wave in waves:
rawWave = [m.value for m in wave.value]
play(rawWave)
function = CombinedFunction(
'synth',
Function(_signature1, _function1),
Function(_signature2, _function2),
Function(_signature3, _function3),
Function(_signature4, _function4),
Function(_signature5, _function5)
)

View File

@@ -1,24 +0,0 @@
import time
from smnp.module.synth.lib.wave import sine
from smnp.note.model import Note
def playNotes(notes, bpm):
for note in notes:
{
Note: play,
int: pause
}[type(note)](note, bpm)
def play(note, bpm):
frequency = note.toFrequency()
duration = 60 * 4 / note.duration / bpm
duration *= 1.5 if note.dot else 1
sine(frequency, duration)
def pause(value, bpm):
time.sleep(60 * 4 / value / bpm)

View File

@@ -1,12 +1,80 @@
import time
import matplotlib.pyplot as plt
import numpy as np
import sounddevice as sd
from smnp.note.pitch import Tuning
from smnp.type.model import Type
FS = 44100
def pause(value, bpm):
time.sleep(60 * 4 / value / bpm)
def plot(wave):
X = np.arange(len(wave))
plt.plot(X, wave)
plt.show()
def play(wave):
sd.play(wave)
time.sleep(len(wave) / FS)
def compilePolyphony(notes, config):
tuning = Tuning(config.tuning)
compiledLines = [1 / len(notes) * compileNotes(line, config, tuning) for line in notes]
return sum(adjustSize(compiledLines))
def adjustSize(compiledLines):
maxSize = max(len(line) for line in compiledLines)
return [np.concatenate([line, np.zeros(maxSize - len(line))]) for line in compiledLines]
def compileNotes(notes, config, tuning):
dispatcher = {
Type.NOTE: lambda note, overtones: sineForNote(note.value, config, tuning),
Type.INTEGER: lambda note, overtones: silenceForPause(note.value, config)
}
return np.concatenate([dispatcher[note.type](note, config) for note in notes])
def sineForNote(note, config, tuning):
frequency = note.toFrequency(tuning)
duration = 60 * 4 / note.duration / config.bpm
duration *= 1.5 if note.dot else 1
return sound(frequency, duration, config)
def sound(frequency, duration, config):
return attack(decay(sum(overtone * sine((i+1) * frequency, duration) for i, overtone in enumerate(config.overtones) if overtone > 0), config), config)
def decay(wave, config):
magnitude = np.exp(-config.decay/len(wave) * np.arange(len(wave)))
return magnitude * wave
def attack(wave, config):
magnitude = -np.exp(-config.attack / len(wave) * np.arange(len(wave)))+1 \
if config.attack > 0 \
else np.ones(len(wave))
return magnitude * wave
def sine(frequency, duration):
samples = (np.sin(2*np.pi*np.arange(FS*duration)*frequency/FS)).astype(np.float32)
sd.play(samples, FS)
time.sleep(duration)
return (np.sin(2 * np.pi * np.arange(FS * duration) * frequency / FS)).astype(np.float32)
def silenceForPause(value, config):
duration = 60 * 4 / value / config.bpm
return np.zeros(int(FS * duration))

View File

@@ -16,8 +16,8 @@ class Note:
self.duration = duration
self.dot = dot
def toFrequency(self):
return self.note.toFrequency() * 2 ** self.octave
def toFrequency(self, tuning):
return tuning[self.note] * 2 ** self.octave
def transpose(self, interval):
origIntRepr = self._intRepr()

View File

@@ -2,6 +2,7 @@ from enum import Enum
from smnp.error.note import NoteException
_semitone = 2**(1/12)
class NotePitch(Enum):
C = 0
@@ -17,21 +18,21 @@ class NotePitch(Enum):
AIS = 10
H = 11
def toFrequency(self):
return {
NotePitch.C: 16.35,
NotePitch.CIS: 17.32,
NotePitch.D: 18.35,
NotePitch.DIS: 19.45,
NotePitch.E: 20.60,
NotePitch.F: 21.83,
NotePitch.FIS: 23.12,
NotePitch.G: 24.50,
NotePitch.GIS: 25.96,
NotePitch.A: 27.50,
NotePitch.AIS: 29.17,
NotePitch.H: 30.87
}[self]
# def toFrequency(self):
# return {
# NotePitch.C: 16.35,
# NotePitch.CIS: 17.32,
# NotePitch.D: 18.35,
# NotePitch.DIS: 19.45,
# NotePitch.E: 20.60,
# NotePitch.F: 21.83,
# NotePitch.FIS: 23.12,
# NotePitch.G: 24.50,
# NotePitch.GIS: 25.96,
# NotePitch.A: 27.50,
# NotePitch.AIS: 29.17,
# NotePitch.H: 30.87
# }[self]
def __str__(self):
return self.name
@@ -50,6 +51,14 @@ class NotePitch(Enum):
raise NoteException(f"Note '{string}' does not exist")
class Tuning(object):
def __init__(self, a4=440):
self.tuning = { value: a4/_semitone**(57-i) for i, value in enumerate(NotePitch.__members__) }
def __getitem__(self, item):
return self.tuning[str(item)]
stringToPitch = {
'cb': NotePitch.H,
'c': NotePitch.C,

View File

@@ -3,7 +3,7 @@ from smnp.ast.node.identifier import Identifier
from smnp.ast.node.list import List
from smnp.ast.node.map import Map
from smnp.error.runtime import RuntimeException
from smnp.runtime.evaluator import Evaluator
from smnp.runtime.evaluator import Evaluator, EvaluationResult
from smnp.runtime.evaluators.expression import expressionEvaluator
from smnp.runtime.evaluators.float import FloatEvaluator
from smnp.runtime.evaluators.iterable import abstractIterableEvaluator
@@ -59,12 +59,15 @@ class MapEvaluator(Evaluator):
@classmethod
def evaluator(cls, node, environment):
map = {}
exprEvaluator = expressionEvaluator(doAssert=True)
keyEvaluator = Evaluator.oneOf(
Evaluator.forNodes(lambda node, environment: EvaluationResult.OK(Type.string(node.value)), Identifier),
expressionEvaluator(doAssert=True)
)
for entry in node.children:
key = exprEvaluator(entry.key, environment).value
key = keyEvaluator(entry.key, environment).value
if key in map:
raise RuntimeException(f"Duplicated key '{key.stringify()}' found in map", entry.pos)
map[key] = exprEvaluator(entry.value, environment).value
map[key] = expressionEvaluator(doAssert=True)(entry.value, environment).value
return Type.map(map)

View File

@@ -1,19 +0,0 @@
from smnp.error.runtime import RuntimeException
from smnp.runtime.evaluator import Evaluator
from smnp.runtime.evaluators.expression import expressionEvaluator
from smnp.type.model import Type
class MapEvaluator(Evaluator):
@classmethod
def evaluator(cls, node, environment):
map = {}
exprEvaluator = expressionEvaluator(doAssert=True)
for entry in node.children:
key = exprEvaluator(entry.key, environment).value
if key in map:
raise RuntimeException(f"Duplicated key '{key.stringify()}' found in map", entry.pos)
map[key] = exprEvaluator(entry.value, environment).value
return Type.map(map)

View File

@@ -12,7 +12,6 @@ class Type(Enum):
STRING = (str, lambda x: x)
LIST = (list, lambda x: f"[{', '.join([e.stringify() for e in x])}]")
MAP = (dict, lambda x: '{' + ', '.join(f"'{k.stringify()}' -> '{v.stringify()}'" for k, v in x.items()) + '}')
PERCENT = (float, lambda x: f"{int(x * 100)}%")
NOTE = (Note, lambda x: x.note.name)
BOOL = (bool, lambda x: str(x).lower())
SOUND = (Sound, lambda x: x.file)