Added support for m-EDO and EDJI notation

This commit is contained in:
2023-03-15 22:53:31 +02:00
parent c30ff41f1f
commit 95b69d1d41
4 changed files with 86 additions and 49 deletions

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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