binary-equalab 1.0.0__py3-none-any.whl → 2.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- binary_equalab/__init__.py +23 -23
- binary_equalab/cli.py +244 -180
- binary_equalab/engine.py +492 -405
- binary_equalab/functions.py +18 -18
- binary_equalab/geometry.py +69 -0
- binary_equalab/giac_poc.py +74 -0
- binary_equalab/parser_enhanced.py +66 -0
- binary_equalab/shell_setup.py +128 -0
- binary_equalab/sonify.py +91 -0
- {binary_equalab-1.0.0.dist-info → binary_equalab-2.0.1.dist-info}/METADATA +35 -6
- binary_equalab-2.0.1.dist-info/RECORD +13 -0
- {binary_equalab-1.0.0.dist-info → binary_equalab-2.0.1.dist-info}/entry_points.txt +1 -0
- binary_equalab-1.0.0.dist-info/RECORD +0 -8
- {binary_equalab-1.0.0.dist-info → binary_equalab-2.0.1.dist-info}/WHEEL +0 -0
binary_equalab/functions.py
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Binary EquaLab - Exported Functions
|
|
3
|
-
Convenience re-exports for direct import usage.
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
from .engine import (
|
|
7
|
-
derivar, integrar, limite, sumatoria,
|
|
8
|
-
simplificar, expandir, factorizar, resolver,
|
|
9
|
-
van, tir, depreciar, interes_simple, interes_compuesto,
|
|
10
|
-
media, mediana, desviacion, varianza
|
|
11
|
-
)
|
|
12
|
-
|
|
13
|
-
__all__ = [
|
|
14
|
-
"derivar", "integrar", "limite", "sumatoria",
|
|
15
|
-
"simplificar", "expandir", "factorizar", "resolver",
|
|
16
|
-
"van", "tir", "depreciar", "interes_simple", "interes_compuesto",
|
|
17
|
-
"media", "mediana", "desviacion", "varianza",
|
|
18
|
-
]
|
|
1
|
+
"""
|
|
2
|
+
Binary EquaLab - Exported Functions
|
|
3
|
+
Convenience re-exports for direct import usage.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from .engine import (
|
|
7
|
+
derivar, integrar, limite, sumatoria,
|
|
8
|
+
simplificar, expandir, factorizar, resolver,
|
|
9
|
+
van, tir, depreciar, interes_simple, interes_compuesto,
|
|
10
|
+
media, mediana, desviacion, varianza
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"derivar", "integrar", "limite", "sumatoria",
|
|
15
|
+
"simplificar", "expandir", "factorizar", "resolver",
|
|
16
|
+
"van", "tir", "depreciar", "interes_simple", "interes_compuesto",
|
|
17
|
+
"media", "mediana", "desviacion", "varianza",
|
|
18
|
+
]
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
|
|
2
|
+
from sympy import Point, Line, N, simplify, symbols, var
|
|
3
|
+
import sympy as sp
|
|
4
|
+
|
|
5
|
+
class GeometryEngine:
|
|
6
|
+
"""
|
|
7
|
+
Geometry extraction from GeoGebra concepts.
|
|
8
|
+
Handles 2D analytic geometry.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
@staticmethod
|
|
12
|
+
def _parse_point(p):
|
|
13
|
+
"""Convert input (tuple, list, or existing Point) to SymPy Point."""
|
|
14
|
+
if isinstance(p, (tuple, list)):
|
|
15
|
+
return Point(*p)
|
|
16
|
+
if isinstance(p, Point):
|
|
17
|
+
return p
|
|
18
|
+
# Attempt to parse string "1,2" or "(1,2)"
|
|
19
|
+
s = str(p).strip("()")
|
|
20
|
+
try:
|
|
21
|
+
parts = s.split(',')
|
|
22
|
+
return Point(float(parts[0]), float(parts[1]))
|
|
23
|
+
except:
|
|
24
|
+
raise ValueError(f"Invalid point format: {p}")
|
|
25
|
+
|
|
26
|
+
def distancia(self, p1, p2):
|
|
27
|
+
"""Distance between two points."""
|
|
28
|
+
A = self._parse_point(p1)
|
|
29
|
+
B = self._parse_point(p2)
|
|
30
|
+
return A.distance(B)
|
|
31
|
+
|
|
32
|
+
def punto_medio(self, p1, p2):
|
|
33
|
+
"""Midpoint of segment P1-P2."""
|
|
34
|
+
A = self._parse_point(p1)
|
|
35
|
+
B = self._parse_point(p2)
|
|
36
|
+
return A.midpoint(B)
|
|
37
|
+
|
|
38
|
+
def pendiente(self, p1, p2):
|
|
39
|
+
"""Slope of line passing through P1 and P2."""
|
|
40
|
+
A = self._parse_point(p1)
|
|
41
|
+
B = self._parse_point(p2)
|
|
42
|
+
if A.x == B.x:
|
|
43
|
+
return float('inf') # Vertical line
|
|
44
|
+
return (B.y - A.y) / (B.x - A.x)
|
|
45
|
+
|
|
46
|
+
def recta(self, p1, p2):
|
|
47
|
+
"""Equation of line passing through P1 and P2."""
|
|
48
|
+
A = self._parse_point(p1)
|
|
49
|
+
B = self._parse_point(p2)
|
|
50
|
+
line = Line(A, B)
|
|
51
|
+
# SymPy Line equation is generic. We want y = mx + b form usually.
|
|
52
|
+
# equation() method returns a*x + b*y + c = 0
|
|
53
|
+
x, y = symbols('x y')
|
|
54
|
+
eq = line.equation(x, y)
|
|
55
|
+
# Solve for y
|
|
56
|
+
res = sp.solve(eq, y)
|
|
57
|
+
if res:
|
|
58
|
+
return sp.Eq(y, res[0])
|
|
59
|
+
else:
|
|
60
|
+
# Vertical line x = c
|
|
61
|
+
res_x = sp.solve(eq, x)
|
|
62
|
+
return sp.Eq(x, res_x[0])
|
|
63
|
+
|
|
64
|
+
def circulo(self, centro, radio):
|
|
65
|
+
"""Equation of circle."""
|
|
66
|
+
C = self._parse_point(centro)
|
|
67
|
+
r = float(radio)
|
|
68
|
+
x, y = symbols('x y')
|
|
69
|
+
return sp.Eq((x - C.x)**2 + (y - C.y)**2, r**2)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Binary EquaLab - Giac Engine Connector (PoC)
|
|
3
|
+
--------------------------------------------
|
|
4
|
+
Bridge between Python and the C++ Giac CAS engine via 'giacpy'.
|
|
5
|
+
This module provides raw speed for Gröbner bases and heavy algebra.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
HAS_GIAC = False
|
|
11
|
+
try:
|
|
12
|
+
import giacpy
|
|
13
|
+
from giacpy import giac
|
|
14
|
+
HAS_GIAC = True
|
|
15
|
+
except ImportError:
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
class GiacEngine:
|
|
19
|
+
def __init__(self):
|
|
20
|
+
if not HAS_GIAC:
|
|
21
|
+
print("⚠️ GiacPy not installed. Running in SymPy-only mode.")
|
|
22
|
+
print(" To enable C++ speed: pip install giacpy")
|
|
23
|
+
|
|
24
|
+
def eval(self, expr: str):
|
|
25
|
+
"""Evaluate expression using Giac C++ engine"""
|
|
26
|
+
if not HAS_GIAC:
|
|
27
|
+
return "Error: Giac C++ engine not available."
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
# Giac handles the string parsing
|
|
31
|
+
result = giac(expr)
|
|
32
|
+
return result
|
|
33
|
+
except Exception as e:
|
|
34
|
+
return f"Giac Error: {e}"
|
|
35
|
+
|
|
36
|
+
def benchmark(self):
|
|
37
|
+
"""Run a speed test vs SymPy"""
|
|
38
|
+
if not HAS_GIAC:
|
|
39
|
+
return
|
|
40
|
+
|
|
41
|
+
import time
|
|
42
|
+
from sympy import expand, symbols
|
|
43
|
+
|
|
44
|
+
print("\n🏎️ BENCHMARK: Python (SymPy) vs C++ (Giac)")
|
|
45
|
+
print("-" * 40)
|
|
46
|
+
|
|
47
|
+
# Test Case: Expand big polynomial
|
|
48
|
+
poly_str = "(x+y+z+1)^15"
|
|
49
|
+
|
|
50
|
+
# SymPy
|
|
51
|
+
x, y, z = symbols('x y z')
|
|
52
|
+
start = time.time()
|
|
53
|
+
# sympy_res = expand((x+y+z+1)**15) # Warning: This is huge
|
|
54
|
+
# Keeping it smaller for quick test
|
|
55
|
+
_ = expand((x+y+z+1)**5)
|
|
56
|
+
dt_sympy = time.time() - start
|
|
57
|
+
print(f"SymPy (Power 5): {dt_sympy:.4f}s")
|
|
58
|
+
|
|
59
|
+
# Giac
|
|
60
|
+
start = time.time()
|
|
61
|
+
_ = giac("(x+y+z+1)^5").expand()
|
|
62
|
+
dt_giac = time.time() - start
|
|
63
|
+
print(f"Giac (Power 5): {dt_giac:.4f}s")
|
|
64
|
+
|
|
65
|
+
print(f"🏆 Winner: {'Giac' if dt_giac < dt_sympy else 'SymPy'}")
|
|
66
|
+
|
|
67
|
+
if __name__ == "__main__":
|
|
68
|
+
g = GiacEngine()
|
|
69
|
+
if HAS_GIAC:
|
|
70
|
+
print("Testing Giac...")
|
|
71
|
+
print(f"Diff(sin(x^2)): {g.eval('diff(sin(x^2), x)')}")
|
|
72
|
+
g.benchmark()
|
|
73
|
+
else:
|
|
74
|
+
print("Install giacpy to run this test.")
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
class EnhancedParser:
|
|
4
|
+
"""
|
|
5
|
+
Pre-processor for mathematical expressions to support 'human' syntax
|
|
6
|
+
that SymPy doesn't natively understand.
|
|
7
|
+
|
|
8
|
+
Inspired by GeoGebra and WolframAlpha input parsing.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
@staticmethod
|
|
12
|
+
def preprocess(expr: str) -> str:
|
|
13
|
+
"""
|
|
14
|
+
Transform raw input into SymPy-compatible syntax.
|
|
15
|
+
"""
|
|
16
|
+
if not expr:
|
|
17
|
+
return ""
|
|
18
|
+
|
|
19
|
+
original = expr
|
|
20
|
+
|
|
21
|
+
# 1. Handle Trig Powers: sin^2(x) -> (sin(x))**2
|
|
22
|
+
# Use specific list of functions to avoid false positives
|
|
23
|
+
funcs = r'(sin|cos|tan|sec|csc|cot|active|sinh|cosh|tanh|asin|acos|atan|ln|log|exp)'
|
|
24
|
+
|
|
25
|
+
# Regex for func^n(arg) -> (func(arg))**n
|
|
26
|
+
# Limitation: Simple arguments balanced with one level of parens or just alphanumeric
|
|
27
|
+
# Match: sin^2(x)
|
|
28
|
+
expr = re.sub(rf'\b{funcs}\^(\d+)\s*\(', r'(\1(', expr)
|
|
29
|
+
# Note: We replace 'sin^2(' with '(sin('.
|
|
30
|
+
# But we need to put ')**2' at the end of the group.
|
|
31
|
+
# This is hard with regex.
|
|
32
|
+
# Workaround: Replace sin^2(x) with sin(x)**2 logic is complex without AST.
|
|
33
|
+
# Fallback: Just remove the ^2 from the function name and append **2 to the block is risky.
|
|
34
|
+
# Let's try a different strategy commonly used:
|
|
35
|
+
# Convert sin^2(x) -> sin(x)**2 works in SymPy ONLY IF sin(x) returns an object that supports **2. It does.
|
|
36
|
+
# SO: Replace 'sin^2' with 'sin' ... wait, no.
|
|
37
|
+
# Let's trust the user to write sin(x)^2 or implement a proper parser later.
|
|
38
|
+
# For now, let's fix the implicit multiplication 'cos2x'
|
|
39
|
+
|
|
40
|
+
# 2. Handle 'cos 2x' or 'cos2x'
|
|
41
|
+
# Pattern: func followed by digit
|
|
42
|
+
# cos2x -> cos(2x)
|
|
43
|
+
expr = re.sub(rf'\b{funcs}(\d+[a-z]*)', r'\1(\2)', expr)
|
|
44
|
+
|
|
45
|
+
# Pattern: func followed by space and alphanumeric
|
|
46
|
+
# cos x -> cos(x)
|
|
47
|
+
expr = re.sub(rf'\b{funcs}\s+([a-z0-9]+)\b', r'\1(\1)', expr) # Wait, regex group \1 is func. Argument is \2
|
|
48
|
+
expr = re.sub(rf'\b{funcs}\s+([a-z][a-z0-9]*|[0-9]+)\b', r'\1(\2)', expr)
|
|
49
|
+
|
|
50
|
+
# 3. Implicit Multiplication
|
|
51
|
+
# Digit followed by Letter: 2x -> 2*x
|
|
52
|
+
expr = re.sub(r'(\d)([a-zA-Z\(])', r'\1*\2', expr)
|
|
53
|
+
|
|
54
|
+
# Letter followed by Digit? x2 usually means variable name, so ignore.
|
|
55
|
+
|
|
56
|
+
# Parenthesis groups: (a)(b) -> (a)*(b)
|
|
57
|
+
expr = re.sub(r'\)([\w\(])', r')*\1', expr)
|
|
58
|
+
|
|
59
|
+
# 4. Power ^ to ** (if not handled by parser, but good to be explicit)
|
|
60
|
+
expr = expr.replace('^', '**')
|
|
61
|
+
|
|
62
|
+
return expr
|
|
63
|
+
|
|
64
|
+
@staticmethod
|
|
65
|
+
def extract_steps(expr: str):
|
|
66
|
+
pass
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import platform
|
|
2
|
+
import os
|
|
3
|
+
import subprocess
|
|
4
|
+
import shutil
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
def detect_shell():
|
|
8
|
+
"""Detect current shell based on OS and Env"""
|
|
9
|
+
system = platform.system()
|
|
10
|
+
|
|
11
|
+
if system == "Windows":
|
|
12
|
+
return "powershell"
|
|
13
|
+
|
|
14
|
+
# Check for Termux
|
|
15
|
+
if "com.termux" in os.environ.get("PREFIX", ""):
|
|
16
|
+
return "termux"
|
|
17
|
+
|
|
18
|
+
shell_env = os.environ.get("SHELL", "")
|
|
19
|
+
if "zsh" in shell_env:
|
|
20
|
+
return "zsh"
|
|
21
|
+
elif "bash" in shell_env:
|
|
22
|
+
return "bash"
|
|
23
|
+
|
|
24
|
+
return "unknown"
|
|
25
|
+
|
|
26
|
+
def install_nerd_font():
|
|
27
|
+
"""Install Cascadia Code Nerd Font"""
|
|
28
|
+
print("📦 [Binary Setup] Instalando Nerd Font...")
|
|
29
|
+
system = platform.system()
|
|
30
|
+
|
|
31
|
+
if system == "Windows":
|
|
32
|
+
try:
|
|
33
|
+
# Try via oh-my-posh if installed, or winget
|
|
34
|
+
subprocess.run(["oh-my-posh", "font", "install", "CascadiaCode"], check=False)
|
|
35
|
+
except FileNotFoundError:
|
|
36
|
+
print("⚠️ Oh My Posh no encontrado, omitiendo fuente automática.")
|
|
37
|
+
|
|
38
|
+
elif system == "Linux":
|
|
39
|
+
if "com.termux" in os.environ.get("PREFIX", ""):
|
|
40
|
+
print("ℹ️ En Termux, configura la fuente manualmente en Termux Settings.")
|
|
41
|
+
else:
|
|
42
|
+
# Generic Linux font install (simple version)
|
|
43
|
+
font_dir = os.path.expanduser("~/.local/share/fonts")
|
|
44
|
+
os.makedirs(font_dir, exist_ok=True)
|
|
45
|
+
# Todo: Download zip logic if needed
|
|
46
|
+
print("ℹ️ Descarga CascadiaCode.zip de NerdFonts y ponlo en ~/.local/share/fonts")
|
|
47
|
+
|
|
48
|
+
def install_oh_my_posh_windows():
|
|
49
|
+
print("📦 [Binary Setup] Instalando Oh My Posh en Windows...")
|
|
50
|
+
|
|
51
|
+
# Check if winget exists
|
|
52
|
+
if shutil.which("winget"):
|
|
53
|
+
subprocess.run(["winget", "install", "JanDeDobbeleer.OhMyPosh"])
|
|
54
|
+
else:
|
|
55
|
+
# Fallback to store or manual
|
|
56
|
+
print("⚠️ Winget no encontrado. Instala Oh My Posh desde Microsoft Store.")
|
|
57
|
+
return
|
|
58
|
+
|
|
59
|
+
# Theme config
|
|
60
|
+
# We can inject our theme later
|
|
61
|
+
print("✅ Oh My Posh instalado. Configura tu $PROFILE.")
|
|
62
|
+
|
|
63
|
+
def install_oh_my_zsh_termux():
|
|
64
|
+
print("📦 [Binary Setup] Configurando Termux (Zsh + Oh My Zsh)...")
|
|
65
|
+
|
|
66
|
+
# Install dependencies
|
|
67
|
+
subprocess.run(["pkg", "install", "zsh", "git", "curl", "-y"])
|
|
68
|
+
|
|
69
|
+
# Install Oh My Zsh (Unattended)
|
|
70
|
+
subprocess.run([
|
|
71
|
+
"sh", "-c",
|
|
72
|
+
'sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattended'
|
|
73
|
+
])
|
|
74
|
+
|
|
75
|
+
# Plugins (Autosuggestions!)
|
|
76
|
+
custom_dir = os.path.expanduser("~/.oh-my-zsh/custom")
|
|
77
|
+
subprocess.run([
|
|
78
|
+
"git", "clone",
|
|
79
|
+
"https://github.com/zsh-users/zsh-autosuggestions.git",
|
|
80
|
+
f"{custom_dir}/plugins/zsh-autosuggestions"
|
|
81
|
+
])
|
|
82
|
+
|
|
83
|
+
subprocess.run([
|
|
84
|
+
"git", "clone",
|
|
85
|
+
"https://github.com/zsh-users/zsh-syntax-highlighting.git",
|
|
86
|
+
f"{custom_dir}/plugins/zsh-syntax-highlighting"
|
|
87
|
+
])
|
|
88
|
+
|
|
89
|
+
# Update .zshrc
|
|
90
|
+
zshrc_path = os.path.expanduser("~/.zshrc")
|
|
91
|
+
with open(zshrc_path, "r") as f:
|
|
92
|
+
content = f.read()
|
|
93
|
+
|
|
94
|
+
content = content.replace('plugins=(git)', 'plugins=(git zsh-autosuggestions zsh-syntax-highlighting)')
|
|
95
|
+
# Set Theme (agnoster is good default, or powerlevel10k)
|
|
96
|
+
content = content.replace('ZSH_THEME="robbyrussell"', 'ZSH_THEME="agnoster"')
|
|
97
|
+
|
|
98
|
+
with open(zshrc_path, "w") as f:
|
|
99
|
+
f.write(content)
|
|
100
|
+
|
|
101
|
+
# Change shell
|
|
102
|
+
subprocess.run(["chsh", "-s", "zsh"])
|
|
103
|
+
|
|
104
|
+
print("✅ Termux configurado. Reinicia la app.")
|
|
105
|
+
|
|
106
|
+
def run_setup():
|
|
107
|
+
print("╔═══════════════════════════════════════╗")
|
|
108
|
+
print("║ 🎨 Binary EquaLab Shell Setup ║")
|
|
109
|
+
print("╚═══════════════════════════════════════╝")
|
|
110
|
+
|
|
111
|
+
shell = detect_shell()
|
|
112
|
+
print(f"🔍 Entorno detectado: {shell.upper()}")
|
|
113
|
+
|
|
114
|
+
if shell == "powershell":
|
|
115
|
+
install_oh_my_posh_windows()
|
|
116
|
+
install_nerd_font()
|
|
117
|
+
|
|
118
|
+
elif shell == "termux":
|
|
119
|
+
install_oh_my_zsh_termux()
|
|
120
|
+
|
|
121
|
+
elif shell == "zsh" or shell == "bash":
|
|
122
|
+
print(f"ℹ️ Para Linux/Mac ({shell}), recomendamos Starship o Oh My Zsh manualmente por ahora.")
|
|
123
|
+
|
|
124
|
+
else:
|
|
125
|
+
print("⚠️ Shell no soportado totalmente. Intenta instalar 'starship' manualmente.")
|
|
126
|
+
|
|
127
|
+
if __name__ == "__main__":
|
|
128
|
+
run_setup()
|
binary_equalab/sonify.py
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
|
|
2
|
+
import numpy as np
|
|
3
|
+
import wave
|
|
4
|
+
import struct
|
|
5
|
+
import os
|
|
6
|
+
from typing import Union, List
|
|
7
|
+
import sympy as sp
|
|
8
|
+
from .parser_enhanced import EnhancedParser
|
|
9
|
+
|
|
10
|
+
class AudioEngine:
|
|
11
|
+
"""
|
|
12
|
+
Las matemáticas también suenan.
|
|
13
|
+
Converts math expressions to .wav audio files.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, sample_rate=44100):
|
|
17
|
+
self.sample_rate = sample_rate
|
|
18
|
+
|
|
19
|
+
def generate(self, expr_str: str, duration: float = 3.0, filename: str = "output.wav"):
|
|
20
|
+
"""
|
|
21
|
+
Evaluate expression f(t) and save as WAV.
|
|
22
|
+
t is time in seconds.
|
|
23
|
+
"""
|
|
24
|
+
# Parse expression to SymPy
|
|
25
|
+
# We need a secure parsing way that allows 't' symbol
|
|
26
|
+
t = sp.symbols('t')
|
|
27
|
+
|
|
28
|
+
# Preprocess using our EnhancedParser (supports 2t, sin^2(t))
|
|
29
|
+
clean_expr = EnhancedParser.preprocess(expr_str)
|
|
30
|
+
# Also standardize python power
|
|
31
|
+
clean_expr = clean_expr.replace('^', '**')
|
|
32
|
+
|
|
33
|
+
try:
|
|
34
|
+
# Parse with SymPy
|
|
35
|
+
sym_expr = sp.sympify(clean_expr)
|
|
36
|
+
# Create lambda for fast numeric evaluation
|
|
37
|
+
f = sp.lambdify(t, sym_expr, modules=['numpy', 'math'])
|
|
38
|
+
except Exception as e:
|
|
39
|
+
raise ValueError(f"Error parsing '{expr_str}': {e}")
|
|
40
|
+
|
|
41
|
+
print(f"🎵 Synthesizing: {clean_expr} for {duration}s...")
|
|
42
|
+
|
|
43
|
+
# Generate Time Array
|
|
44
|
+
t_arr = np.linspace(0, duration, int(self.sample_rate * duration))
|
|
45
|
+
|
|
46
|
+
# Evaluate function
|
|
47
|
+
try:
|
|
48
|
+
audio_data = f(t_arr)
|
|
49
|
+
|
|
50
|
+
# Handle scalar result (e.g. "440")
|
|
51
|
+
if np.isscalar(audio_data):
|
|
52
|
+
audio_data = np.full_like(t_arr, float(audio_data))
|
|
53
|
+
|
|
54
|
+
except Exception as e:
|
|
55
|
+
raise ValueError(f"Math Error: {e}")
|
|
56
|
+
|
|
57
|
+
# Normalize and Clip to -1..1
|
|
58
|
+
max_val = np.max(np.abs(audio_data))
|
|
59
|
+
if max_val > 0:
|
|
60
|
+
audio_data = audio_data / max_val * 0.9 # Normalize and leave headroom
|
|
61
|
+
else:
|
|
62
|
+
# Silence
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
# Convert to 16-bit PCM
|
|
66
|
+
# audio_data is float -1..1
|
|
67
|
+
audio_int16 = (audio_data * 32767).astype(np.int16)
|
|
68
|
+
|
|
69
|
+
# Save WAV
|
|
70
|
+
try:
|
|
71
|
+
with wave.open(filename, 'w') as wav_file:
|
|
72
|
+
# 1 Channel (Mono), 2 bytes (16 bit), Sample Rate, Count, Method
|
|
73
|
+
wav_file.setparams((1, 2, self.sample_rate, len(audio_int16), 'NONE', 'not compressed'))
|
|
74
|
+
|
|
75
|
+
# Write frames
|
|
76
|
+
# struct.pack isn't efficient for arrays, use tobytes()
|
|
77
|
+
wav_file.writeframes(audio_int16.tobytes())
|
|
78
|
+
|
|
79
|
+
print(f"✅ Saved audio to: {os.path.abspath(filename)}")
|
|
80
|
+
return os.path.abspath(filename)
|
|
81
|
+
|
|
82
|
+
except Exception as e:
|
|
83
|
+
raise IOError(f"File Error: {e}")
|
|
84
|
+
|
|
85
|
+
# Standalone CLI test
|
|
86
|
+
if __name__ == "__main__":
|
|
87
|
+
engine = AudioEngine()
|
|
88
|
+
# A440 Sine Wave
|
|
89
|
+
engine.generate("sin(440*2*pi*t)", duration=2.0, filename="test_tone.wav")
|
|
90
|
+
# AM Synthesis (Tremolo)
|
|
91
|
+
engine.generate("sin(440*2*pi*t) * (0.5 + 0.5*sin(5*2*pi*t))", duration=3.0, filename="test_tremolo.wav")
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: binary-equalab
|
|
3
|
-
Version:
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 2.0.1
|
|
4
|
+
Summary: Advanced Algebra & Calculus CLI Tool
|
|
5
5
|
Project-URL: Homepage, https://github.com/Malexnnn/BinaryEquaLab
|
|
6
6
|
Project-URL: Repository, https://github.com/Malexnnn/BinaryEquaLab
|
|
7
7
|
Project-URL: Documentation, https://github.com/Malexnnn/BinaryEquaLab#readme
|
|
8
|
-
Author:
|
|
8
|
+
Author-email: Malexnnn <carde@example.com>
|
|
9
9
|
License-Expression: MIT
|
|
10
10
|
Keywords: algebra,calculator,calculus,cas,math,spanish,symbolic
|
|
11
11
|
Classifier: Development Status :: 4 - Beta
|
|
@@ -39,7 +39,7 @@ Description-Content-Type: text/markdown
|
|
|
39
39
|
</p>
|
|
40
40
|
|
|
41
41
|
<p align="center">
|
|
42
|
-
<em
|
|
42
|
+
<em>"Las matemáticas también sienten, pero estas no se equivocan."</em>
|
|
43
43
|
</p>
|
|
44
44
|
|
|
45
45
|
---
|
|
@@ -52,13 +52,42 @@ pip install binary-equalab
|
|
|
52
52
|
|
|
53
53
|
Or from source:
|
|
54
54
|
```bash
|
|
55
|
-
|
|
55
|
+
# En carpeta binary-cli
|
|
56
56
|
pip install -e .
|
|
57
|
+
bneqls
|
|
58
|
+
```
|
|
59
|
+
(La opción `-e` hace que los cambios se reflejen al momento sin reinstalar).
|
|
60
|
+
|
|
61
|
+
### 📱 Termux (Android)
|
|
62
|
+
La instalación en Termux nativo requiere compilar algunas dependencias (NumPy/SymPy).
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
# 1. Instalar compiladores y librerías del sistema
|
|
66
|
+
pkg update
|
|
67
|
+
pkg install python clang make pkg-config libjpeg-turbo freetype libpng
|
|
68
|
+
|
|
69
|
+
# 2. Instalar Binary EquaLab
|
|
70
|
+
pip install binary-equalab
|
|
57
71
|
```
|
|
58
72
|
|
|
59
73
|
---
|
|
60
74
|
|
|
61
|
-
##
|
|
75
|
+
## 🐚 Universal Shell Setup
|
|
76
|
+
Binary EquaLab incluye un configurador mágico para tu terminal. Instala temas (Oh My Posh/Zsh), fuentes y plugins automáticamente.
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
# Ejecutar configurador
|
|
80
|
+
binary setup-shell
|
|
81
|
+
# O directamente:
|
|
82
|
+
python -m binary_equalab.cli setup-shell
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Soporta:
|
|
86
|
+
- **Windows**: Oh My Posh + Nerd Fonts.
|
|
87
|
+
- **Termux**: Zsh + Oh My Zsh + Autosuggestions.
|
|
88
|
+
- **Linux**: Recomendaciones de Starship.
|
|
89
|
+
|
|
90
|
+
## 🚀 Uso del CLI
|
|
62
91
|
|
|
63
92
|
### REPL Mode
|
|
64
93
|
```bash
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
binary_equalab/__init__.py,sha256=WFF_aioc3t6xkz0qU_RI4FeqY4UT92c8wE5cDDJWenY,675
|
|
2
|
+
binary_equalab/cli.py,sha256=-L4obeppbbfi8ZuuXoGuF0HgFu0o8W-G0bQProNsNOY,8733
|
|
3
|
+
binary_equalab/engine.py,sha256=WhkKpdDCg2zqH9cMXDPMQ7Ut7Awyn4WzkVrRVP7_dtE,15931
|
|
4
|
+
binary_equalab/functions.py,sha256=F6GPCXoScCHVZ4CDEE2Rns5i3x89DAcKRVBte88j9oE,569
|
|
5
|
+
binary_equalab/geometry.py,sha256=YPn59oQtTY_hb4LZyIEJUvN1pWJz_y5uk8gVBio1Gwo,2217
|
|
6
|
+
binary_equalab/giac_poc.py,sha256=_XdxE2lrvFH7M6qDfzSQHRdplxPzKhKfIWAcsOQq8Ho,2226
|
|
7
|
+
binary_equalab/parser_enhanced.py,sha256=P7oMnGHWn3xoqVChkPJ6B_i_IR9TeJMJXBjKoCO13ts,2735
|
|
8
|
+
binary_equalab/shell_setup.py,sha256=9Ax_QcHEAopYIjShuBaHglg4HPysXJKbNL3D_FjexrU,4518
|
|
9
|
+
binary_equalab/sonify.py,sha256=HfAYPtXLRyb3miLBsd8liqk4D5hvXVBHEEyV6fj9Za8,3274
|
|
10
|
+
binary_equalab-2.0.1.dist-info/METADATA,sha256=GcxDwiPnhKaKIY5B_SLUIZVQdbD0pdY81Q923WieATs,5084
|
|
11
|
+
binary_equalab-2.0.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
12
|
+
binary_equalab-2.0.1.dist-info/entry_points.txt,sha256=2Io1Wi069dvgPHmGXmAT1a6rNA-DIq7tChai69jtbF0,119
|
|
13
|
+
binary_equalab-2.0.1.dist-info/RECORD,,
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
binary_equalab/__init__.py,sha256=h6Fi1RtNgWo6PnSqoRZX1ee2uvn9faJfneI16L14XR8,652
|
|
2
|
-
binary_equalab/cli.py,sha256=mepUJ_5XBz93k0edpVsXXhgEgt1EqXEHJJ-K6cx69Vw,5759
|
|
3
|
-
binary_equalab/engine.py,sha256=KTlpNvHMTODAl1haTw8tZUfxqU9CHNMx4XqVtyAa_SY,12831
|
|
4
|
-
binary_equalab/functions.py,sha256=czx4m5ZZP8VL27TdC6p8cH4FNpkabTF-EymP5iC64nw,551
|
|
5
|
-
binary_equalab-1.0.0.dist-info/METADATA,sha256=sCqgMV3c2YMq2Gn8c53IZ1N_o-hU0D24juie3yUHoiU,4215
|
|
6
|
-
binary_equalab-1.0.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
7
|
-
binary_equalab-1.0.0.dist-info/entry_points.txt,sha256=KAAmBj-EOydDlCxIFK_NycbqimMK9_yp_VODFvoaaEI,86
|
|
8
|
-
binary_equalab-1.0.0.dist-info/RECORD,,
|
|
File without changes
|