webpd
Version:
WebPd is a compiler for audio programming language Pure Data allowing to run .pd patches on web pages.
207 lines (182 loc) • 8.02 kB
JavaScript
import { interpolateLin } from './points.js';
import { Sequence, Class, Var, Func, ConstVar } from '../../../node_modules/@webpd/compiler/dist/src/ast/declare.js';
const linesUtils = {
namespace: 'linesUtils',
// prettier-ignore
code: ({ ns: linesUtils }, { points }) => Sequence([
Class(linesUtils.LineSegment, [
Var(points.Point, `p0`),
Var(points.Point, `p1`),
Var(`Float`, `dx`),
Var(`Float`, `dy`),
]),
Func(linesUtils.computeSlope, [
Var(points.Point, `p0`),
Var(points.Point, `p1`)
], 'Float') `
return p1.x !== p0.x ? (p1.y - p0.y) / (p1.x - p0.x) : 0
`,
Func(linesUtils.removePointsBeforeFrame, [
Var(`Array<${points.Point}>`, `points`),
Var(`Float`, `frame`)
], `Array<${points.Point}>`) `
${ConstVar(`Array<${points.Point}>`, `newPoints`, `[]`)}
${Var(`Int`, `i`, `0`)}
while (i < points.length) {
if (frame <= points[i].x) {
newPoints.push(points[i])
}
i++
}
return newPoints
`,
Func(linesUtils.insertNewLinePoints, [
Var(`Array<${points.Point}>`, `points`),
Var(points.Point, `p0`),
Var(points.Point, `p1`)
], `Array<${points.Point}>`) `
${ConstVar(`Array<${points.Point}>`, `newPoints`, `[]`)}
${Var(`Int`, `i`, `0`)}
// Keep the points that are before the new points added
while (i < points.length && points[i].x <= p0.x) {
newPoints.push(points[i])
i++
}
// Find the start value of the start point :
// 1. If there is a previous point and that previous point
// is on the same frame, we don't modify the start point value.
// (represents a vertical line).
if (0 < i - 1 && points[i - 1].x === p0.x) {
// 2. If new points are inserted in between already existing points
// we need to interpolate the existing line to find the startValue.
} else if (0 < i && i < points.length) {
newPoints.push({
x: p0.x,
y: ${points.interpolateLin}(p0.x, points[i - 1], points[i])
})
// 3. If new line is inserted after all existing points,
// we just take the value of the last point
} else if (i >= points.length && points.length) {
newPoints.push({
x: p0.x,
y: points[points.length - 1].y,
})
// 4. If new line placed in first position, we take the defaultStartValue.
} else if (i === 0) {
newPoints.push({
x: p0.x,
y: p0.y,
})
}
newPoints.push({
x: p1.x,
y: p1.y,
})
return newPoints
`,
Func(linesUtils.computeFrameAjustedPoints, [
Var(`Array<${points.Point}>`, `points`)
], `Array<${points.Point}>`) `
if (points.length < 2) {
throw new Error('invalid length for points')
}
${ConstVar(`Array<${points.Point}>`, `newPoints`, `[]`)}
${Var(`Int`, `i`, `0`)}
${Var(points.Point, `p`, `points[0]`)}
${Var(`Float`, `frameLower`, `0`)}
${Var(`Float`, `frameUpper`, `0`)}
while(i < points.length) {
p = points[i]
frameLower = Math.floor(p.x)
frameUpper = frameLower + 1
// I. Placing interpolated point at the lower bound of the current frame
// ------------------------------------------------------------------------
// 1. Point is already on an exact frame,
if (p.x === frameLower) {
newPoints.push({ x: p.x, y: p.y })
// 1.a. if several of the next points are also on the same X,
// we find the last one to draw a vertical line.
while (
(i + 1) < points.length
&& points[i + 1].x === frameLower
) {
i++
}
if (points[i].y !== newPoints[newPoints.length - 1].y) {
newPoints.push({ x: points[i].x, y: points[i].y })
}
// 1.b. if last point, we quit
if (i + 1 >= points.length) {
break
}
// 1.c. if next point is in a different frame we can move on to next iteration
if (frameUpper <= points[i + 1].x) {
i++
continue
}
// 2. Point isn't on an exact frame
// 2.a. There's a previous point, the we use it to interpolate the value.
} else if (newPoints.length) {
newPoints.push({
x: frameLower,
y: ${points.interpolateLin}(frameLower, points[i - 1], p),
})
// 2.b. It's the very first point, then we don't change its value.
} else {
newPoints.push({ x: frameLower, y: p.y })
}
// II. Placing interpolated point at the upper bound of the current frame
// ---------------------------------------------------------------------------
// First, we find the closest point from the frame upper bound (could be the same p).
// Or could be a point that is exactly placed on frameUpper.
while (
(i + 1) < points.length
&& (
Math.ceil(points[i + 1].x) === frameUpper
|| Math.floor(points[i + 1].x) === frameUpper
)
) {
i++
}
p = points[i]
// 1. If the next point is directly in the next frame,
// we do nothing, as this corresponds with next iteration frameLower.
if (Math.floor(p.x) === frameUpper) {
continue
// 2. If there's still a point after p, we use it to interpolate the value
} else if (i < points.length - 1) {
newPoints.push({
x: frameUpper,
y: ${points.interpolateLin}(frameUpper, p, points[i + 1]),
})
// 3. If it's the last point, we dont change the value
} else {
newPoints.push({ x: frameUpper, y: p.y })
}
i++
}
return newPoints
`,
Func(linesUtils.computeLineSegments, [
Var(`Array<${points.Point}>`, `points`)
], `Array<${linesUtils.LineSegment}>`) `
${ConstVar(`Array<${linesUtils.LineSegment}>`, `lineSegments`, `[]`)}
${Var(`Int`, `i`, `0`)}
${Var(points.Point, `p0`)}
${Var(points.Point, `p1`)}
while(i < points.length - 1) {
p0 = points[i]
p1 = points[i + 1]
lineSegments.push({
p0, p1,
dy: ${linesUtils.computeSlope}(p0, p1),
dx: 1,
})
i++
}
return lineSegments
`,
]),
dependencies: [interpolateLin],
};
export { linesUtils };