Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions build_config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@
# Use Object class extension
conf.gem :core => "mruby-object-ext"

# Use Enum class extension
conf.gem :core => "mruby-enum-ext"

# Use toplevel object (main) methods extension
conf.gem :core => "mruby-toplevel-ext"

Expand Down
1 change: 1 addition & 0 deletions src/mruby-zest/example/ZynControllers.qml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ Group {
Col {
ToggleButton { extern: ctrl.extern+"fmamp.receive"}
ToggleButton { extern: ctrl.extern+"sustain.receive"}
ToggleButton { label: "stops Portamnt." extern: ctrl.extern+"sustain.stopsPortamento"}
}
}
}
4 changes: 2 additions & 2 deletions src/mruby-zest/example/ZynEnvEdit.qml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Widget {
return if env.selected == nil
return if !free.value
path = self.extern + "addPoint"
$remote.action(path, env.selected)
$remote.action(path, env.selected/3)
env.refresh
}
function del_point()
Expand All @@ -16,7 +16,7 @@ Widget {
return if env.selected == nil
return if !free.value
path = self.extern + "delPoint"
$remote.action(path, env.selected)
$remote.action(path, env.selected/3)
env.refresh
}

Expand Down
22 changes: 15 additions & 7 deletions src/mruby-zest/example/ZynOscilModRight.qml
Original file line number Diff line number Diff line change
Expand Up @@ -153,38 +153,46 @@ Widget {
function onSetup(old=nil)
{
vce = root.get_view_pos(:voice)

if vce == 0
sync.active = false
else
sync.active = true
sync.active = true
end

mapper = [-1]
mapper2 = [-2]
names = ["Normal"]
names2 = ["Normal"]
names2 = ["PartFbk"]
names2 << "Normal"
mapper2 << -1
(0...vce).each do |i|
mapper << i
mapper2 << i
names << "Oscil #{i+1}"
names2 << "Mod #{i+1}"
end
mapper2 << vce
names2 << "Mod #{vce+1}"



extfm.opt_vals = mapper
extfm.options = names
extfm.extern = base.extern + "PextFMoscil"
ext.opt_vals = mapper
ext.options = names
ext.extern = base.extern + "Pextoscil"
extmod.opt_vals = mapper
extmod.opt_vals = mapper2
extmod.options = names2
extmod.extern = base.extern + "PFMVoice"

}

function updateFMVoice(old=nil)
{
vce = root.get_view_pos(:voice)
if sync.value == false
if sync.value == false
mapper = [-1]
names2 = ["Normal"]
extmod.selected = extmod.selected + 1
Expand All @@ -205,6 +213,6 @@ Widget {
if(extmod.valueRef && extmod.selected <= extmod.opt_vals.length)
extmod.valueRef.value = extmod.opt_vals[extmod.selected]
end

}
}
5 changes: 5 additions & 0 deletions src/mruby-zest/example/ZynPortamento.qml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ Group {
extern: port.extern+"portamento.portamento"
label: "enable"
}
Selector {
extern: port.extern+"portamento.polyMode"
label: "mode"
}
Knob { extern: port.extern+"portamento.glissando"}
Knob { extern: port.extern+"portamento.time"}
Knob { extern: port.extern+"portamento.updowntimestretch"}
}
Expand Down
231 changes: 211 additions & 20 deletions src/mruby-zest/mrblib/draw-common.rb
Original file line number Diff line number Diff line change
Expand Up @@ -180,39 +180,174 @@ def self.lfo_plot(vg, bb, dat, stroke)
vg.stroke_width 1.0
end

def self.env_plot(vg, bb, dat, stroke, selected)
# Bezier interpolation using the NEW backend interface
def self.bezier_interpolate(a, b_offs, c_offs, d, t)
diff = (d - a)

# New base calculation (matches backend)
b = a + diff * (0.3333333333 + b_offs)
c = a + diff * (0.6666666666 + c_offs)

mt = 1.0 - t
mt2 = mt * mt
t2 = t * t

return mt*mt2*a + 3.0*mt2*t*b + 3.0*mt*t2*c + t*t2*d
end

# Main plotting function for envelope visualization
def self.env_plot(vg, bb, dat, stroke, selected, emode, c_offsets)
n = dat.length
pts = 32

vg.path do
vg.move_to(bb.x + bb.w*dat[0].x,
bb.y + bb.h/2*(1-dat[0].y))
(0...n).each do |i|
vg.line_to(bb.x + bb.w*dat[i].x,
bb.y + bb.h/2*(1-dat[i].y))
# Move to starting point
vg.move_to(bb.x + bb.w * dat[0].x,
bb.y + bb.h / 2 * (1 - dat[0].y))

# Each Bezier segment requires 4 points: start, control1, control2, end
segments = (dat.length - 1) / 3

(0...segments).each do |s|
# 1. Identify indices
h = 3 * s # Anchor A
i = h + 1 # CP 1
j = h + 2 # CP 2
k = h + 3 # Anchor D

# 2. Extract anchor y-values
a = dat[h].y
d = dat[k].y

# 3. GET OFFSETS DIRECTLY
# Matching the backend's releaseindex-based indexing:
# Segment s corresponds to releaseindex = s + 1
b_offs = c_offsets[(s + 1) * 2 - 1] || 0.0
c_offs = c_offsets[(s + 1) * 2] || 0.0

# Move to start of this segment
vg.move_to(bb.x + bb.w * dat[h].x,
bb.y + bb.h / 2 * (1 - a))

# Sample the curve with specified number of points
(1...pts).each do |pt|
# Normalized interpolation parameter [0, 1]
t = pt.to_f / pts.to_f

# Special handling for first segment in certain envelope modes
if h == 0
case emode
when 1, 2 # ADSR_lin or ADSR_dB (logarithmic scaling)
v1 = (10.0 ** a - 0.01) / 0.99
v2 = (10.0 ** d - 0.01) / 0.99
rap = v1 + (v2 - v1) * t
y = Math.log10(rap * 0.99 + 0.01)

when 3 # ASR_freqlfo (frequency LFO with special scaling)
v1 = (2.0 ** (6.0 * a.abs) - 1)
v1 = -v1 if a < 0
b_offs = -b_offs if a < 0

v2 = (2.0 ** (6.0 * d.abs) - 1)
v2 = -v2 if d < 0
c_offs = -c_offs if d < 0

rap = v1 + (v2 - v1) * t

y = if rap >= 0
Math.log(rap + 1.0) / (6.0 * Math.log(2.0))
else
-Math.log(1.0 - rap) / (6.0 * Math.log(2.0))
end

else # ADSR_filter = 4; ASR_bw = 5 (linear interpolation)
y = a + (d - a) * t
end

# Calculate x-position (linear interpolation between segment boundaries)
x_pos = bb.x + bb.w * dat[h].x + bb.w * (dat[k].x - dat[h].x) * t
y_pos = bb.y + bb.h / 2.0 * (1.0 - y)
vg.line_to(x_pos, y_pos)

else
# Standard Bezier interpolation using the new interface
y = bezier_interpolate(a, b_offs, c_offs, d, t)

# Calculate x-position (linear interpolation between segment boundaries)
x_pos = bb.x + bb.w * dat[h].x + bb.w * (dat[k].x - dat[h].x) * t
y_pos = bb.y + bb.h / 2.0 * (1.0 - y)
vg.line_to(x_pos, y_pos)
end
end

# Ensure line reaches the exact end point
vg.line_to(bb.x + bb.w * dat[k].x,
bb.y + bb.h / 2.0 * (1.0 - d))
end

# Apply styling to the path
vg.line_join NVG::ROUND
vg.stroke_width 2.0
vg.stroke_color stroke
vg.stroke
end
vg.stroke_width 1.0
end

def self.env_draw_markers(vg, bb, dat, stroke, selected, emode)
n = dat.length

sel_color = Theme::VisualSelect
bright = Theme::VisualBright
bright2 = Theme::VisualBright2

sel_color = Theme::VisualSelect
bright = Theme::VisualBright
(0...n).each do |i|
xx = bb.x + bb.w*dat[i].x;
yy = bb.y + bb.h/2*(1-dat[i].y);
scale = 3
vg.stroke_color sel_color if(selected == i)
vg.stroke_color bright if(selected != i)
vg.fill_color Theme::EnvelopePoint
Draw::WaveForm::env_marker(vg, xx, yy, scale)
next if([1,2].include?(i))
# Determine if this is an anchor or a control point
is_anchor = (i % 3 == 0)

xx = bb.x + bb.w * dat[i].x
yy = bb.y + bb.h / 2 * (1 - dat[i].y)

# UI Scaling: anchors are slightly larger than CPs
scale = is_anchor ? 4 : 2.5
type = is_anchor ? :anchor : :cp

# Color logic
if (selected == i)
vg.stroke_color sel_color
elsif is_anchor
vg.stroke_color bright
else
vg.stroke_color bright2
end

vg.fill_color Theme::EnvelopePoint

# Draw the specific marker type
env_marker(vg, xx, yy, scale, type)
end
end

def self.env_marker(vg, x, y, scale)

# Draws a marker: Square for anchors, Rhombus for control points
def self.env_marker(vg, x, y, scale, type = :anchor)
return if x.nan? || y.nan?
vg.path do
vg.translate(0.5, 0.5)
vg.rect((x-scale).round(),(y-scale).round(),(scale*2).round(),(scale*2).round());

if type == :anchor
# Standard Square
vg.rect((x - scale).round, (y - scale).round, (scale * 2).round, (scale * 2).round)
else
# Rhombus (Diamond shape)
# We define the 4 corners: Top, Right, Bottom, Left
vg.move_to(x, y - scale - 1) # Top
vg.line_to(x + scale + 1, y) # Right
vg.line_to(x, y + scale + 1) # Bottom
vg.line_to(x - scale - 1, y) # Left
vg.close_path
end

vg.stroke_width 1.0
vg.fill
vg.stroke
Expand Down Expand Up @@ -279,7 +414,7 @@ def self.log_y(vg, min, max, bb)
(0...10).each do |shift|
delta = Math.log((shift+1)*1.0)/(log10*(max_-min_))
dy = bb.h*(base+delta);

next if(dy < 0 || dy > bb.h)
vg.path do |v|
v.move_to(bb.x, bb.y+dy);
Expand Down Expand Up @@ -362,7 +497,7 @@ def self.linear_y(vg, min, max, bb, thick=1.0, c=40)
med_fill = Theme::GridLine
light_fill = Theme::GridLine
c = max

x = (bb.x).floor
y = (bb.y).floor
w = (bb.w).floor
Expand Down Expand Up @@ -696,6 +831,60 @@ def self.zipToPos(x,y)
o
end

def self.zipToPosEnv(x,y,c)
o = []
n = [x.length, y.length, c.length/2].min
o << Pos.new(x[0], y[0])
(1...n).each do |i|
xcp1 = (x[i]+3*x[i-1])/4
ycp1 = (y[i]+3*y[i-1])/4 + 2*c[i*2-1]
o << Pos.new(xcp1, ycp1)
xcp2 = (3*x[i]+x[i-1])/4
ycp2 = (3*y[i]+y[i-1])/4 + 2*c[i*2]
o << Pos.new(xcp2, ycp2)

o << Pos.new(x[i], y[i])
end
o
end

def self.zipToPosCP(x, y, c_offsets)
o = []
n = [x.length, y.length, c_offsets.length / 2 + 1].min
o << Pos.new(x[0], y[0])

# This factor brings the diamonds closer to the linear path
# 1.0 = mathematical control point
# 0.7 = closer to the curve
visual_strength = 0.6

(1...n).each do |i|
x_start, y_start = x[i-1], y[i-1]
x_end, y_end = x[i], y[i]

x_diff = x_end - x_start
y_diff = y_end - y_start

cp1_x = x_start + x_diff * 0.33333333
cp2_x = x_start + x_diff * 0.66666666

# Fetch backend offsets
# Use the backend indexing: (s+1)*2-1 and (s+1)*2
b_offs = c_offsets[i * 2 - 1] || 0.0
c_offs = c_offsets[i * 2] || 0.0

# Apply visual_strength to the offset part of the formula
# Original: (y_start + y_diff * 0.33) + (b_offs * y_diff)
cp1_y = (y_start + y_diff * 0.33333333) + (b_offs * y_diff * visual_strength)
cp2_y = (y_start + y_diff * 0.66666666) + (c_offs * y_diff * visual_strength)

o << Pos.new(cp1_x, cp1_y)
o << Pos.new(cp2_x, cp2_y)
o << Pos.new(x_end, y_end)
end
o
end

def self.toPos(p)
o = []
n = p.length/2
Expand Down Expand Up @@ -774,6 +963,7 @@ module Theme

SustainPoint = color("005f8a")
EnvelopePoint = color("232c36")
EnvelopeCPoint = color("23362c")

#Keyboard Widget
KeyWhiteGrad1 = color("B0B7C0")
Expand All @@ -790,6 +980,7 @@ module Theme
VisualStroke = color("014767")
VisualLightFill = color("014767",55)
VisualBright = color("3ac5ec")
VisualBright2 = color("3ac560")
VisualDim = color("143644")
VisualDimTrans = color("143644", 155)
VisualSelect = color("00ff00")
Expand Down
Loading