Added support for m-EDO and EDJI notation
This commit is contained in:
@ -172,7 +172,7 @@ class Pitch(Event):
|
|||||||
"""Class for pitch in time"""
|
"""Class for pitch in time"""
|
||||||
|
|
||||||
pitch_class: int
|
pitch_class: int
|
||||||
pitch_bend: float = field(default=None)
|
pitch_bend: int = field(default=None)
|
||||||
octave: int = field(default=None)
|
octave: int = field(default=None)
|
||||||
modifier: int = field(default=None)
|
modifier: int = field(default=None)
|
||||||
note: int = field(default=None)
|
note: int = field(default=None)
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
""" Lark transformer for mapping Lark tokens to Ziffers objects """
|
""" Lark transformer for mapping Lark tokens to Ziffers objects """
|
||||||
import random
|
import random
|
||||||
from math import log
|
from math import log, pow
|
||||||
from lark import Transformer, Token
|
from lark import Transformer, Token
|
||||||
from .scale import cents_to_semitones
|
from .scale import cents_to_semitones, ratio_to_cents
|
||||||
from .classes.root import Ziffers
|
from .classes.root import Ziffers
|
||||||
from .classes.sequences import (
|
from .classes.sequences import (
|
||||||
Sequence,
|
Sequence,
|
||||||
@ -484,36 +484,57 @@ class ZiffersTransformer(Transformer):
|
|||||||
# pylint: disable=locally-disabled, unused-argument, too-many-public-methods, invalid-name
|
# pylint: disable=locally-disabled, unused-argument, too-many-public-methods, invalid-name
|
||||||
class ScalaTransformer(Transformer):
|
class ScalaTransformer(Transformer):
|
||||||
def lines(self, items):
|
def lines(self, items):
|
||||||
return cents_to_semitones(items)
|
cents = [ratio_to_cents(item) if isinstance(item,int) else item for item in items]
|
||||||
|
return cents_to_semitones(cents)
|
||||||
|
|
||||||
def operation(self, items):
|
def operation(self, items):
|
||||||
val = eval("".join(str(item) for item in items))
|
val = eval("".join(str(item) for item in items))
|
||||||
return 1200.0 * log(float(val), 2)
|
return val
|
||||||
|
|
||||||
def operator(self, items):
|
def operator(self, items):
|
||||||
return items[0].value
|
return items[0].value
|
||||||
|
|
||||||
def sub_operations(self, items):
|
def sub_operations(self, items):
|
||||||
return "(" + items[0] + ")"
|
return "(" + items[0] + ")"
|
||||||
|
|
||||||
def number(self, items):
|
def ratio(self, items):
|
||||||
val = items[0]
|
ratio = items[0]/items[1]
|
||||||
return float(val.value)
|
return ratio_to_cents(ratio)
|
||||||
|
|
||||||
def random(self, items):
|
def edo_ratio(self, items):
|
||||||
def _parse_type(val):
|
ratio = pow(2,items[0]/items[1])
|
||||||
if "." in val:
|
return ratio_to_cents(ratio)
|
||||||
return float(val)
|
|
||||||
else:
|
def edji_ratio(self, items):
|
||||||
return int(val)
|
if len(items)>3:
|
||||||
|
power = items[2]/items[3]
|
||||||
|
else:
|
||||||
|
power = items[2]
|
||||||
|
ratio = pow(power,items[0]/items[1])
|
||||||
|
return ratio_to_cents(ratio)
|
||||||
|
|
||||||
|
def int(self, items):
|
||||||
|
return int(items[0].value)
|
||||||
|
|
||||||
|
def float(self, items):
|
||||||
|
return float(items[0].value)
|
||||||
|
|
||||||
|
def random_int(self, items):
|
||||||
|
|
||||||
def _rand_between(start, end):
|
def _rand_between(start, end):
|
||||||
if isinstance(start, float) or isinstance(end, float):
|
return random.randint(min(start, end), max(start, end))
|
||||||
return random.uniform(min(start, end), max(start, end))
|
|
||||||
elif isinstance(start, int) and isinstance(end, int):
|
|
||||||
return random.randint(min(start, end), max(start, end))
|
|
||||||
|
|
||||||
start = _parse_type(items[0].value)
|
start = items[0]
|
||||||
end = _parse_type(items[1].value)
|
end = items[1]
|
||||||
rand_val = _rand_between(start, end)
|
rand_val = _rand_between(start, end)
|
||||||
return 1200.0 * log(float(rand_val), 2)
|
return rand_val
|
||||||
|
|
||||||
|
def random_decimal(self, items):
|
||||||
|
|
||||||
|
def _rand_between(start, end):
|
||||||
|
return random.uniform(min(start, end), max(start, end))
|
||||||
|
|
||||||
|
start = items[0]
|
||||||
|
end = items[1]
|
||||||
|
rand_val = _rand_between(start, end)
|
||||||
|
return rand_val
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# pylint: disable=locally-disabled, no-name-in-module
|
# pylint: disable=locally-disabled, no-name-in-module
|
||||||
import re
|
import re
|
||||||
from math import log2, floor
|
from math import log2, floor, log
|
||||||
from .common import repeat_text
|
from .common import repeat_text
|
||||||
from .defaults import (
|
from .defaults import (
|
||||||
SCALES,
|
SCALES,
|
||||||
@ -177,7 +177,10 @@ def note_from_pc(
|
|||||||
|
|
||||||
note = note + (octave * sum(intervals)) + modifier
|
note = note + (octave * sum(intervals)) + modifier
|
||||||
|
|
||||||
return resolve_pitch_bend(note)
|
if isinstance(note, float):
|
||||||
|
return resolve_pitch_bend(note)
|
||||||
|
|
||||||
|
return (note, None)
|
||||||
|
|
||||||
|
|
||||||
def parse_roman(numeral: str) -> int:
|
def parse_roman(numeral: str) -> int:
|
||||||
@ -286,14 +289,14 @@ def midi_to_pitch_class(note: int, key: str | int, scale: str) -> dict:
|
|||||||
if len(npc) > 1:
|
if len(npc) > 1:
|
||||||
modifier = 1 if (npc[0] == "#") else -1
|
modifier = 1 if (npc[0] == "#") else -1
|
||||||
return {
|
return {
|
||||||
"text": repeat_text("^", "_", octave)+npc,
|
"text": repeat_text("^", "_", octave) + npc,
|
||||||
"pitch_class": int(npc[1]),
|
"pitch_class": int(npc[1]),
|
||||||
"octave": octave,
|
"octave": octave,
|
||||||
"modifier": modifier,
|
"modifier": modifier,
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"text": repeat_text("^", "_", octave)+npc,
|
"text": repeat_text("^", "_", octave) + npc,
|
||||||
"pitch_class": int(npc),
|
"pitch_class": int(npc),
|
||||||
"octave": octave,
|
"octave": octave,
|
||||||
}
|
}
|
||||||
@ -326,7 +329,11 @@ def chord_from_degree(
|
|||||||
|
|
||||||
|
|
||||||
def named_chord_from_degree(
|
def named_chord_from_degree(
|
||||||
degree: int, name: str = "major", root: int = 60, scale: str="Major", num_octaves: int = 1
|
degree: int,
|
||||||
|
name: str = "major",
|
||||||
|
root: int = 60,
|
||||||
|
scale: str = "Major",
|
||||||
|
num_octaves: int = 1,
|
||||||
) -> list[int]:
|
) -> list[int]:
|
||||||
"""Generates chord from given roman numeral and chord name
|
"""Generates chord from given roman numeral and chord name
|
||||||
|
|
||||||
@ -339,14 +346,15 @@ def named_chord_from_degree(
|
|||||||
list[int]: _description_
|
list[int]: _description_
|
||||||
"""
|
"""
|
||||||
intervals = CHORDS.get(name, CHORDS["major"])
|
intervals = CHORDS.get(name, CHORDS["major"])
|
||||||
scale_degree = get_scale_notes(scale, root)[degree-1]
|
scale_degree = get_scale_notes(scale, root)[degree - 1]
|
||||||
notes = []
|
notes = []
|
||||||
for cur_oct in range(num_octaves):
|
for cur_oct in range(num_octaves):
|
||||||
for interval in intervals:
|
for interval in intervals:
|
||||||
notes.append(scale_degree + interval + (cur_oct * 12))
|
notes.append(scale_degree + interval + (cur_oct * 12))
|
||||||
return notes
|
return notes
|
||||||
|
|
||||||
def resolve_pitch_bend(note_value: float, semitones: int=1) -> int:
|
|
||||||
|
def resolve_pitch_bend(note_value: float, semitones: int = 1) -> int:
|
||||||
"""Resolves pitch bend value from float midi note
|
"""Resolves pitch bend value from float midi note
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -356,23 +364,29 @@ def resolve_pitch_bend(note_value: float, semitones: int=1) -> int:
|
|||||||
Returns:
|
Returns:
|
||||||
int: Returns pitch bend value ranging from 0 to 16383. 8192 means no bend.
|
int: Returns pitch bend value ranging from 0 to 16383. 8192 means no bend.
|
||||||
"""
|
"""
|
||||||
# TODO: None or 8192
|
midi_bend_value = 8192
|
||||||
midi_bend_value = None
|
|
||||||
if isinstance(note_value, float) and note_value % 1 != 0.0:
|
if isinstance(note_value, float) and note_value % 1 != 0.0:
|
||||||
start_value = note_value if note_value > round(note_value) else round(note_value)
|
start_value = (
|
||||||
|
note_value if note_value > round(note_value) else round(note_value)
|
||||||
|
)
|
||||||
end_value = round(note_value) if note_value > round(note_value) else note_value
|
end_value = round(note_value) if note_value > round(note_value) else note_value
|
||||||
bend_diff = midi_to_freq(start_value) / midi_to_freq(end_value)
|
bend_diff = midi_to_freq(start_value) / midi_to_freq(end_value)
|
||||||
bend_target = 1200 * log2(bend_diff)
|
bend_target = 1200 * log2(bend_diff)
|
||||||
# https://www.cs.cmu.edu/~rbd/doc/cmt/part7.html
|
# https://www.cs.cmu.edu/~rbd/doc/cmt/part7.html
|
||||||
midi_bend_value = 8192 + int(8191 * (bend_target/(100*semitones)))
|
midi_bend_value = 8192 + int(8191 * (bend_target / (100 * semitones)))
|
||||||
return (note_value, midi_bend_value)
|
return (note_value, midi_bend_value)
|
||||||
|
|
||||||
|
|
||||||
def cents_to_semitones(cents):
|
def cents_to_semitones(cents: list) -> tuple[float]:
|
||||||
|
"""Tranform cents to semitones"""
|
||||||
if cents[0] != 0.0:
|
if cents[0] != 0.0:
|
||||||
cents = [0.0]+cents
|
cents = [0.0] + cents
|
||||||
semitone_scale = []
|
semitone_scale = []
|
||||||
for i, cent in enumerate(cents[:-1]):
|
for i, cent in enumerate(cents[:-1]):
|
||||||
semitone_interval = (cents[i+1] - cent) / 100
|
semitone_interval = (cents[i + 1] - cent) / 100
|
||||||
semitone_scale.append(semitone_interval)
|
semitone_scale.append(semitone_interval)
|
||||||
return tuple(semitone_scale)
|
return tuple(semitone_scale)
|
||||||
|
|
||||||
|
def ratio_to_cents(ratio: float) -> float:
|
||||||
|
"""Transform ratio to cents"""
|
||||||
|
return 1200.0 * log(float(ratio), 2)
|
||||||
|
|||||||
@ -1,18 +1,20 @@
|
|||||||
?root: lines
|
?root: lines
|
||||||
|
|
||||||
lines: (number | operation)+
|
lines: (number | operation | ratio | edo_ratio)+
|
||||||
random: "(" SIGNED_NUMBER "," SIGNED_NUMBER ")"
|
|
||||||
operation: number (operator (number | sub_operations | operation))+
|
operation: number (operator (number | sub_operations | operation))+
|
||||||
!operator: "+" | "-" | "*" | "%" | "&" | "|" | "<<" | ">>" | "/"
|
ratio: (int | random_int) "/" (int | random_int)
|
||||||
|
edo_ratio: (int | random_int) "\\" (int | random_int)
|
||||||
|
edji_ratio: (int | random_int) "\\" (int | random_int) "<" int "/" int ">"
|
||||||
|
!operator: "+" | "-" | "*" | "%" | "&" | "|" | "<<" | ">>"
|
||||||
sub_operations: "(" operation ")"
|
sub_operations: "(" operation ")"
|
||||||
number: SIGNED_NUMBER | random
|
|
||||||
|
|
||||||
%import common.SIGNED_NUMBER
|
// Signed number without EXP
|
||||||
%import common.FLOAT
|
?number: float | int | random_int | random_float
|
||||||
%import common.INT
|
random_int: "(" int "," int ")"
|
||||||
%import common.WORD
|
random_float: "(" float "," float ")"
|
||||||
%import common.CNAME
|
float: /(-?[0-9]+\.[0-9]*)|(\.[0-9]+)/
|
||||||
|
int: /[0-9]+/
|
||||||
|
|
||||||
%import common.WS
|
%import common.WS
|
||||||
%import common.NEWLINE
|
|
||||||
|
|
||||||
%ignore WS
|
%ignore WS
|
||||||
Reference in New Issue
Block a user