summaryrefslogtreecommitdiff
path: root/src/mml_composemusic_mcp/lexer.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/mml_composemusic_mcp/lexer.py')
-rw-r--r--src/mml_composemusic_mcp/lexer.py140
1 files changed, 136 insertions, 4 deletions
diff --git a/src/mml_composemusic_mcp/lexer.py b/src/mml_composemusic_mcp/lexer.py
index 94876bf..e5f08a8 100644
--- a/src/mml_composemusic_mcp/lexer.py
+++ b/src/mml_composemusic_mcp/lexer.py
@@ -17,6 +17,7 @@ class TokenType(Enum):
TEMPO = auto()
GATE_TIME = auto()
TIE = auto()
+ TIE_CMD = auto() # ^ (ppmck tie)
REPEAT_START = auto()
REPEAT_END = auto()
BAR = auto()
@@ -29,7 +30,26 @@ class TokenType(Enum):
DOT = auto()
AT = auto()
TRANSPOSE = auto()
- DETUNE = auto()
+ DETUNE = auto() # D (ppmck detune)
+ SWEEP = auto() # s (ppmck sweep)
+ QUANTIZE = auto() # q (ppmck quantize)
+ REL_VOL_UP = auto() # v+ (ppmck)
+ REL_VOL_DOWN = auto() # v- (ppmck)
+ DUTY_ENV_USE = auto() # @@ (ppmck)
+ VOL_ENV = auto() # @v (ppmck)
+ LFO_DEF = auto() # @MP (ppmck)
+ LFO_USE = auto() # MP (ppmck)
+ LFO_OFF = auto() # MPOF (ppmck)
+ PITCH_ENV_DEF = auto() # @EP (ppmck)
+ PITCH_ENV_USE = auto() # EP (ppmck)
+ PITCH_ENV_OFF = auto() # EPOF (ppmck)
+ NOTE_ENV_DEF = auto() # @EN (ppmck)
+ NOTE_ENV_USE = auto() # EN (ppmck)
+ NOTE_ENV_OFF = auto() # ENOF (ppmck)
+ BRACE_OPEN = auto() # { (ppmck envelope def)
+ BRACE_CLOSE = auto() # } (ppmck envelope def)
+ EQUAL = auto() # = (ppmck envelope def)
+ COMMA = auto() # , (ppmck sweep/envelope def)
EXT_CMD = auto() # @ENV, @VIB, @GLI
INVALID = auto() # unrecognised character
EOF = auto()
@@ -151,6 +171,10 @@ class Lexer:
self._advance()
self._emit(TokenType.TIE, "&", "&")
return
+ if ch == "^":
+ self._advance()
+ self._emit(TokenType.TIE_CMD, "^", "^")
+ return
if ch == "[":
self._advance()
self._emit(TokenType.REPEAT_START, "[", "[")
@@ -160,6 +184,22 @@ class Lexer:
count = self._read_number()
self._emit(TokenType.REPEAT_END, count, "]" + count)
return
+ if ch == "{":
+ self._advance()
+ self._emit(TokenType.BRACE_OPEN, "{", "{")
+ return
+ if ch == "}":
+ self._advance()
+ self._emit(TokenType.BRACE_CLOSE, "}", "}")
+ return
+ if ch == "=":
+ self._advance()
+ self._emit(TokenType.EQUAL, "=", "=")
+ return
+ if ch == ",":
+ self._advance()
+ self._emit(TokenType.COMMA, ",", ",")
+ return
if ch == ".":
self._advance()
self._emit(TokenType.DOT, ".", ".")
@@ -176,6 +216,40 @@ class Lexer:
self._advance()
self._emit(TokenType.FLAT, "-", "-")
return
+ if ch == "@":
+ self._advance()
+ next_ch = self._peek()
+ if next_ch == "@":
+ self._advance()
+ num = self._read_number()
+ self._emit(TokenType.DUTY_ENV_USE, num, "@@" + num)
+ return
+ if next_ch == "v":
+ self._advance()
+ num = self._read_number()
+ self._emit(TokenType.VOL_ENV, num, "@v" + num)
+ return
+ if next_ch == "M" and self._peek(1) == "P":
+ self._advance()
+ self._advance()
+ num = self._read_number()
+ self._emit(TokenType.LFO_DEF, num, "@MP" + num)
+ return
+ if next_ch == "E" and self._peek(1) == "P":
+ self._advance()
+ self._advance()
+ num = self._read_number()
+ self._emit(TokenType.PITCH_ENV_DEF, num, "@EP" + num)
+ return
+ if next_ch == "E" and self._peek(1) == "N":
+ self._advance()
+ self._advance()
+ num = self._read_number()
+ self._emit(TokenType.NOTE_ENV_DEF, num, "@EN" + num)
+ return
+ num = self._read_number()
+ self._emit(TokenType.DUTY, num, "@" + num)
+ return
if ch.isdigit():
num = self._read_number()
self._emit(TokenType.NUMBER, num, num)
@@ -185,9 +259,53 @@ class Lexer:
self._emit(TokenType.TRACK_HEADER, raw.upper(), raw)
return
if ch.isalpha():
+ # 2-character commands: MP, EP, EN and their OFF variants
+ if ch == "M" and self._peek(1) == "P":
+ self._advance()
+ self._advance()
+ if self._peek() == "O" and self._peek(1) == "F":
+ self._advance()
+ self._advance()
+ self._emit(TokenType.LFO_OFF, "MPOF", "MPOF")
+ else:
+ num = self._read_number()
+ self._emit(TokenType.LFO_USE, num, "MP" + num)
+ return
+ if ch == "E" and self._peek(1) == "P":
+ self._advance()
+ self._advance()
+ if self._peek() == "O" and self._peek(1) == "F":
+ self._advance()
+ self._advance()
+ self._emit(TokenType.PITCH_ENV_OFF, "EPOF", "EPOF")
+ else:
+ num = self._read_number()
+ self._emit(TokenType.PITCH_ENV_USE, num, "EP" + num)
+ return
+ if ch == "E" and self._peek(1) == "N":
+ self._advance()
+ self._advance()
+ if self._peek() == "O" and self._peek(1) == "F":
+ self._advance()
+ self._advance()
+ self._emit(TokenType.NOTE_ENV_OFF, "ENOF", "ENOF")
+ else:
+ num = self._read_number()
+ self._emit(TokenType.NOTE_ENV_USE, num, "EN" + num)
+ return
+
cmd = self._advance()
lower = cmd.lower()
- if lower in "olvtq":
+ if lower in "olvt":
+ if lower == "v" and self._peek() in "+-":
+ sign = self._advance()
+ num = self._read_number()
+ raw = cmd + sign + num
+ if sign == "+":
+ self._emit(TokenType.REL_VOL_UP, num, raw)
+ else:
+ self._emit(TokenType.REL_VOL_DOWN, num, raw)
+ return
num = self._read_number()
raw = cmd + num
if lower == "o":
@@ -198,8 +316,22 @@ class Lexer:
self._emit(TokenType.VOLUME, num, raw)
elif lower == "t":
self._emit(TokenType.TEMPO, num, raw)
- elif lower == "q":
- self._emit(TokenType.DUTY, num, raw)
+ return
+ if lower == "q":
+ num = self._read_number()
+ self._emit(TokenType.QUANTIZE, num, "q" + num)
+ return
+ if cmd == "D":
+ num = self._read_signed_number()
+ self._emit(TokenType.DETUNE, num, "D" + num)
+ return
+ if lower == "s":
+ num0 = self._read_number()
+ if self._peek() == ",":
+ self._advance()
+ num1 = self._read_number()
+ raw = f"s{num0},{num1}"
+ self._emit(TokenType.SWEEP, f"{num0},{num1}", raw)
return
note = lower
if note in "cdefgabr":