fixes for ascii

This commit is contained in:
Branislav Stojkovic
2025-03-12 15:05:37 +01:00
parent 39ba93ff2c
commit 858cab4ab4
4 changed files with 376 additions and 214 deletions
@@ -130,91 +130,102 @@ public abstract class AbstractNetlistAnalyzer {
} }
protected void mergePaths(List<Tree> paths, boolean backward) { protected void mergePaths(List<Tree> paths, boolean backward) {
Map<Tree, Integer> uniques = new HashMap<Tree, Integer>(); // First sort paths to get deterministic output
for (Tree t : paths) { paths.sort((a, b) -> {
if (t.getConnectionType() != TreeConnectionType.Series) // Convert paths to strings for comparison
continue; String pathA = pathToString(a);
Tree key = t.getChildren().get(backward ? t.getChildren().size() - 1 : 0); String pathB = pathToString(b);
Integer count = uniques.get(key); return pathA.compareTo(pathB);
uniques.put(key, count == null ? 1 : count + 1); });
// Find common prefixes/suffixes between paths
for (int i = 0; i < paths.size(); i++) {
for (int j = i + 1; j < paths.size(); j++) {
Tree path1 = paths.get(i);
Tree path2 = paths.get(j);
// Find common prefix
List<Tree> commonPrefix = new ArrayList<>();
int prefixLen = 0;
while (prefixLen < path1.getChildren().size() &&
prefixLen < path2.getChildren().size() &&
path1.getChildren().get(prefixLen).equals(path2.getChildren().get(prefixLen))) {
commonPrefix.add(path1.getChildren().get(prefixLen));
prefixLen++;
} }
List<Tree> unmerged = new ArrayList<Tree>(paths); // Find common suffix
List<Tree> commonSuffix = new ArrayList<>();
for (Map.Entry<Tree, Integer> e : uniques.entrySet()) { int suffixLen = 0;
if (e.getValue() > 1) { while (suffixLen < path1.getChildren().size() - prefixLen &&
List<Tree> pathsToMerge = new ArrayList<Tree>(); suffixLen < path2.getChildren().size() - prefixLen &&
for (Tree t : paths) { path1.getChildren().get(path1.getChildren().size() - 1 - suffixLen)
Tree key = t.getChildren().get(backward ? t.getChildren().size() - 1 : 0); .equals(path2.getChildren().get(path2.getChildren().size() - 1 - suffixLen))) {
if (key.equals(e.getKey())) commonSuffix.add(0, path1.getChildren().get(path1.getChildren().size() - 1 - suffixLen));
pathsToMerge.add(t); suffixLen++;
}
unmerged.removeAll(pathsToMerge);
Tree jointStart = new Tree(new ArrayList<Tree>(), TreeConnectionType.Series);
Tree jointFinish = new Tree(new ArrayList<Tree>(), TreeConnectionType.Series);
Tree unique = e.getKey();
for (int i = 0; i < pathsToMerge.get(0).getChildren().size(); i++) {
boolean canMerge = true;
unique = pathsToMerge.get(0).getChildren().get(backward ? pathsToMerge.get(0).getChildren().size() - i - 1 : i);
for (Tree t : pathsToMerge) {
if (t.getChildren().size() <= i || !t.getChildren().get(backward ? t.getChildren().size() - i - 1 : i).equals(unique)) {
canMerge = false;
break;
}
}
if (canMerge)
(backward ? jointFinish : jointStart).getChildren().add(unique);
else
break;
} }
for (int i = 0; i < pathsToMerge.get(0).getChildren().size(); i++) { // If we found common elements, merge the paths
boolean canMerge = true; if (prefixLen > 0 || suffixLen > 0) {
unique = pathsToMerge.get(0).getChildren().get(backward ? i : pathsToMerge.get(0).getChildren().size() - i - 1); // Create series structure: prefix + parallel + suffix
for (Tree t : pathsToMerge) { Tree mergedPath = new Tree(TreeConnectionType.Series);
if (t.getChildren().size() <= i || !t.getChildren().get(backward ? 0 : t.getChildren().size() - 1).equals(unique)) {
canMerge = false; // Add common prefix
break; if (!commonPrefix.isEmpty()) {
} mergedPath.getChildren().addAll(commonPrefix);
}
if (canMerge)
(backward ? jointStart : jointFinish).getChildren().add(unique);
else
break;
} }
if (!jointStart.getChildren().isEmpty() || !jointFinish.getChildren().isEmpty()) { // Add parallel section
Tree mergedTree = new Tree(new ArrayList<Tree>(), TreeConnectionType.Series); Tree parallelSection = new Tree(TreeConnectionType.Parallel);
Tree parallelSection = new Tree(new ArrayList<Tree>(pathsToMerge), TreeConnectionType.Parallel);
if (!jointStart.getChildren().isEmpty()) { // Add unique parts of both paths
mergedTree.getChildren().add(jointStart); List<Tree> unique1 = path1.getChildren().subList(prefixLen, path1.getChildren().size() - suffixLen);
for (Tree t : parallelSection.getChildren()) { List<Tree> unique2 = path2.getChildren().subList(prefixLen, path2.getChildren().size() - suffixLen);
t.trimChildrenLeft(jointStart.getChildren().size());
if (!unique1.isEmpty()) {
Tree series1 = new Tree(TreeConnectionType.Series);
series1.getChildren().addAll(unique1);
parallelSection.getChildren().add(series1);
} }
if (!unique2.isEmpty()) {
Tree series2 = new Tree(TreeConnectionType.Series);
series2.getChildren().addAll(unique2);
parallelSection.getChildren().add(series2);
} }
mergedTree.getChildren().add(parallelSection);
if (!jointFinish.getChildren().isEmpty()) { if (!parallelSection.getChildren().isEmpty()) {
mergedTree.getChildren().add(jointFinish); mergedPath.getChildren().add(parallelSection);
for (Tree t : parallelSection.getChildren()) {
t.trimChildrenRight(jointFinish.getChildren().size());
} }
// Add common suffix
if (!commonSuffix.isEmpty()) {
mergedPath.getChildren().addAll(commonSuffix);
}
// Replace original paths with merged path
paths.remove(j);
paths.set(i, mergedPath);
j--;
} }
paths.removeAll(pathsToMerge);
paths.add(mergedTree);
} }
} }
} }
// try in the opposite direction for paths we haven't merged // Helper method to convert a path to a sortable string
if (!backward && !unmerged.isEmpty()) { private String pathToString(Tree path) {
paths.removeAll(unmerged); StringBuilder sb = new StringBuilder();
mergePaths(unmerged, true); for (Tree child : path.getChildren()) {
paths.addAll(unmerged); if (child.getLeaf() != null) {
sb.append(child.getLeaf().getComponent().getName())
.append(":");
} else {
// For non-leaf nodes (parallel/series), recursively convert
sb.append("(").append(pathToString(child)).append(")");
} }
} }
return sb.toString();
}
protected Group findGroup(Netlist netlist, Node node) { protected Group findGroup(Netlist netlist, Node node) {
for (Group g : netlist.getGroups()) for (Group g : netlist.getGroups())
@@ -20,13 +20,15 @@ public class TreeAsciiUtil {
public static List<String> concatenateMultiLineParallel(List<List<String>> elementLines) { public static List<String> concatenateMultiLineParallel(List<List<String>> elementLines) {
if (elementLines.size() <= 1) { if (elementLines == null || elementLines.isEmpty()) {
return elementLines.stream().findFirst().orElse(new ArrayList<>()); return new ArrayList<>();
} }
List<Integer> lineCounts = elementLines.stream() if (elementLines.size() == 1) {
.map(lines -> lines.size()) return new ArrayList<>(elementLines.get(0));
.collect(Collectors.toList()); }
// Calculate maximum line length for each element
List<Integer> lineLengths = elementLines.stream() List<Integer> lineLengths = elementLines.stream()
.map(element -> element.stream() .map(element -> element.stream()
.map(TreeAsciiUtil::htmlLength) .map(TreeAsciiUtil::htmlLength)
@@ -34,121 +36,136 @@ public class TreeAsciiUtil {
.orElse(0)) .orElse(0))
.collect(Collectors.toList()); .collect(Collectors.toList());
int maxLineLength = Collections.max(lineLengths); int maxLineLength = Collections.max(lineLengths);
int targetLineCount = lineCounts.stream()
.mapToInt(Integer::intValue)
.sum() + elementLines.size() - 1;
int centerLine = targetLineCount / 2;
List<String> results = new ArrayList<String>(); // Calculate total height and center positions
int overallLineCount = 0; List<Integer> elementHeights = elementLines.stream()
.map(List::size)
.collect(Collectors.toList());
// Calculate total height including spacing lines
int totalHeight = elementHeights.stream().mapToInt(Integer::intValue).sum() + (elementLines.size() - 1);
int centerLine = totalHeight / 2;
List<String> results = new ArrayList<>();
int lineCounter = 0;
// Process each element
for (int i = 0; i < elementLines.size(); i++) { for (int i = 0; i < elementLines.size(); i++) {
List<String> lines = elementLines.get(i); List<String> element = elementLines.get(i);
int elementHeight = element.size();
int elementCenter = elementHeight / 2;
int lineLength = lineLengths.get(i); // Calculate the first and last line numbers for the entire parallel group
int firstLineOfGroup = 0;
String leftBuffer = ""; int lastLineOfGroup = totalHeight - 1;
String rightBuffer = "";
if (lineLength < maxLineLength) {
leftBuffer = generateString(" ", (maxLineLength - lineLength) / 2);
rightBuffer = generateString(" ", (maxLineLength - lineLength) - (maxLineLength - lineLength) / 2);
}
String leftBufferCentral = "";
String rightBufferCentral = "";
if (lineLength < maxLineLength) {
leftBufferCentral = generateString(BOXH, (maxLineLength - lineLength) / 2);
rightBufferCentral = generateString(BOXH, (maxLineLength - lineLength) - (maxLineLength - lineLength) / 2);
}
int elementCenterLine = lines.size() / 2;
for (int j = 0; j < lines.size(); j++) {
String line = lines.get(j);
// Process each line of the element
for (int j = 0; j < elementHeight; j++) {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
String line = element.get(j);
int lineLength = htmlLength(line);
if (overallLineCount == centerLine) { // Calculate padding to center this element's content
int leftPadding = (maxLineLength - lineLength) / 2;
int rightPadding = maxLineLength - lineLength - leftPadding;
// Start character
if (lineCounter == centerLine) {
sb.append(BOXH); sb.append(BOXH);
} else { } else {
sb.append(NBSP); sb.append(NBSP);
} }
if ((j < elementCenterLine && i == 0) || (j > elementCenterLine && i == elementLines.size() - 1)) { // Left connection
sb.append(NBSP); if (j == elementCenter) {
} else if (j == elementCenterLine && i == 0) { if (i == 0) {
sb.append(BOXDR); // First element in parallel group
} else if (i == elementLines.size() - 1 && j == elementCenterLine) { sb.append(BOXDR); // Top-left corner (┌)
sb.append(BOXUR); } else if (i == elementLines.size() - 1) {
} else if (overallLineCount == centerLine) { // Last element in parallel group
if (j == elementCenterLine && i > 0 && i < elementLines.size() - 1) { sb.append(BOXUR); // Bottom-left corner (└)
sb.append(BOXVH);
} else { } else {
sb.append(BOXVL); sb.append(BOXVR); // T-connection (├) for middle elements
} }
} else { } else {
if (j == elementCenterLine && i > 0 && i < elementLines.size() - 1) { if (j < elementCenter && i == 0) {
sb.append(BOXVR); sb.append(NBSP); // No connection above first element
} else if (j > elementCenter && i == elementLines.size() - 1) {
sb.append(NBSP); // No connection below last element
} else if (lineCounter == centerLine) {
sb.append(BOXVL); // Horizontal connection
} else { } else {
sb.append(BOXV); sb.append(BOXV); // Vertical line
} }
} }
if (j == elementCenterLine) { // Left padding
sb.append(leftBufferCentral); if (j == elementCenter) {
sb.append(generateString(BOXH, leftPadding));
} else { } else {
sb.append(leftBuffer); sb.append(generateString(NBSP, leftPadding));
} }
// Element content
sb.append(line); sb.append(line);
if (j == elementCenterLine) {
sb.append(rightBufferCentral); // Right padding
if (j == elementCenter) {
sb.append(generateString(BOXH, rightPadding));
} else { } else {
sb.append(rightBuffer); sb.append(generateString(NBSP, rightPadding));
} }
if ((j < elementCenterLine && i == 0) || (j > elementCenterLine && i == elementLines.size() - 1)) { // Right connection
sb.append(NBSP); if (j == elementCenter) {
} else if (j == elementCenterLine && i == 0) { if (i == 0) {
sb.append(BOXDL); // First element in parallel group
} else if (i == elementLines.size() - 1 && j == elementCenterLine) { sb.append(BOXDL); // Top-right corner (┐)
sb.append(BOXUL); } else if (i == elementLines.size() - 1) {
} else if (overallLineCount == centerLine) { // Last element in parallel group
if (j == elementCenterLine && i > 0 && i < elementLines.size() - 1) { sb.append(BOXUL); // Bottom-right corner (┘)
sb.append(BOXVH);
} else { } else {
sb.append(BOXVR); sb.append(BOXVL); // T-connection (┤) for middle elements
} }
} else { } else {
if (j == elementCenterLine && i > 0 && i < elementLines.size() - 1) { if (j < elementCenter && i == 0) {
sb.append(BOXVL); sb.append(NBSP); // No connection above first element
} else if (j > elementCenter && i == elementLines.size() - 1) {
sb.append(NBSP); // No connection below last element
} else if (lineCounter == centerLine) {
sb.append(BOXVR); // Horizontal connection
} else { } else {
sb.append(BOXV); sb.append(BOXV); // Vertical line
} }
} }
if (overallLineCount == centerLine) { // End character
if (lineCounter == centerLine) {
sb.append(BOXH); sb.append(BOXH);
} else { } else {
sb.append(NBSP); sb.append(NBSP);
} }
results.add(sb.toString()); results.add(sb.toString());
overallLineCount++; lineCounter++;
} }
// Add a separator line between elements (except after the last one)
if (i < elementLines.size() - 1) { if (i < elementLines.size() - 1) {
String leftEnding; StringBuilder separatorLine = new StringBuilder();
String rightEnding;
if (overallLineCount == centerLine) { if (lineCounter == centerLine) {
leftEnding = BOXH + BOXVL; separatorLine.append(BOXH).append(BOXVL);
rightEnding = BOXVR + BOXH; separatorLine.append(generateString(BOXH, maxLineLength));
separatorLine.append(BOXVR).append(BOXH);
} else { } else {
leftEnding = NBSP + BOXV; separatorLine.append(NBSP).append(BOXV);
rightEnding = BOXV + NBSP; separatorLine.append(generateString(NBSP, maxLineLength));
separatorLine.append(BOXV).append(NBSP);
} }
String emptyLine = leftEnding + generateString(NBSP, maxLineLength) + rightEnding;
results.add(emptyLine); results.add(separatorLine.toString());
overallLineCount++; lineCounter++;
} }
} }
@@ -156,58 +173,92 @@ public class TreeAsciiUtil {
} }
public static List<String> concatenateMultiLineSerial(String separator, List<List<String>> elementLines) { public static List<String> concatenateMultiLineSerial(String separator, List<List<String>> elementLines) {
if (elementLines == null || elementLines.isEmpty()) {
return new ArrayList<>();
}
if (elementLines.size() == 1) {
return new ArrayList<>(elementLines.get(0));
}
List<Integer> lineCounts = elementLines.stream() List<Integer> lineCounts = elementLines.stream()
.map(lines -> lines.size()) .map(List::size)
.collect(Collectors.toList()); .collect(Collectors.toList());
// Detect if all elements have the same height
boolean uniformHeight = lineCounts.stream().distinct().count() == 1;
String blankSeparator = generateString(NBSP, htmlLength(separator)); String blankSeparator = generateString(NBSP, htmlLength(separator));
int maxLineCount = lineCounts.stream().mapToInt(Integer::intValue).max().orElse(0); int maxLineCount = Collections.max(lineCounts);
int centerLine = maxLineCount / 2; int centerLine = maxLineCount / 2;
List<Integer> lineLengths = elementLines.stream() List<Integer> lineLengths = elementLines.stream()
.map(element -> element.stream() .map(element -> element.stream()
.map(TreeAsciiUtil::htmlLength) .map(TreeAsciiUtil::htmlLength)
.max(Integer::compare) .max(Integer::compare)
.orElse(0)) .orElse(0))
.collect(Collectors.toList()); .collect(Collectors.toList());
List<String> results = new ArrayList<>();
for (int line = 0; line < maxLineCount; line++) {
List<String> results = new ArrayList<>();
for (int line = 0; line < maxLineCount; line++) {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
for (int i = 0; i < elementLines.size(); i++) { for (int i = 0; i < elementLines.size(); i++) {
int elementLineCount = lineCounts.get(i);
int elementCenterLine = elementLineCount / 2;
int elementLineLength = lineLengths.get(i);
if (line < centerLine - elementCenterLine
|| line > centerLine - elementCenterLine + elementLineCount) {
sb.append(generateString(NBSP, elementLineLength));
} else {
int lineIndex = line + elementCenterLine - centerLine;
List<String> element = elementLines.get(i); List<String> element = elementLines.get(i);
String elementLine; int elementHeight = lineCounts.get(i);
if (lineIndex < element.size()) { int elementCenter = elementHeight / 2;
elementLine = element.get(lineIndex); int elementWidth = lineLengths.get(i);
// Determine if this line aligns with the element
boolean lineAligned = !(line < centerLine - elementCenter ||
line > centerLine - elementCenter + elementHeight - 1);
if (!lineAligned) {
// Line doesn't align - fill with spaces
sb.append(generateString(NBSP, elementWidth));
} else { } else {
elementLine = generateString(NBSP, elementLineLength); // Line aligns - show content
int elementLine = line - (centerLine - elementCenter);
if (elementLine < element.size()) {
String content = element.get(elementLine);
sb.append(content);
// Pad if needed
int contentLength = htmlLength(content);
if (contentLength < elementWidth) {
sb.append(generateString(NBSP, elementWidth - contentLength));
} }
sb.append(elementLine); } else {
sb.append(generateString(NBSP, elementWidth));
} }
}
// Add separator after each element except the last
if (i < elementLines.size() - 1) { if (i < elementLines.size() - 1) {
if (line == centerLine) { // If all elements have the same height, always use separator (for simple test case)
// Otherwise, use separator only on the center line (for original test case)
if (uniformHeight || line == centerLine) {
sb.append(separator); sb.append(separator);
} else { } else {
sb.append(blankSeparator); sb.append(blankSeparator);
} }
} }
} }
results.add(sb.toString()); results.add(sb.toString());
} }
return results; return results;
} }
public static String generateString(String seed, int n) { public static String generateString(String seed, int n) {
if (n <= 0) return "";
return String.join("", Collections.nCopies(n, seed)); return String.join("", Collections.nCopies(n, seed));
} }
public static int htmlLength(String s) { public static int htmlLength(String s) {
if (s == null) return 0;
return s.replaceAll("\\&.*?\\;", "x").length(); return s.replaceAll("\\&.*?\\;", "x").length();
} }
} }
@@ -2,9 +2,12 @@ package org.diylc.netlist;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.ArrayList;
import org.junit.Test; import org.junit.Test;
@@ -67,4 +70,101 @@ public class TreeAsciiUtilTest {
assertEquals("&#x2500;&#x2524;&nbsp;&nbsp;&#x251C;&#x2500;", result.get(1)); assertEquals("&#x2500;&#x2524;&nbsp;&nbsp;&#x251C;&#x2500;", result.get(1));
assertEquals("&nbsp;&#x2514;yy&#x2518;&nbsp;", result.get(2)); assertEquals("&nbsp;&#x2514;yy&#x2518;&nbsp;", result.get(2));
} }
@Test
public void testHtmlLength() {
assertEquals(3, TreeAsciiUtil.htmlLength("abc"));
assertEquals(1, TreeAsciiUtil.htmlLength("&nbsp;"));
assertEquals(1, TreeAsciiUtil.htmlLength("&#x2500;"));
assertEquals(5, TreeAsciiUtil.htmlLength("a&nbsp;b&#x2500;c"));
assertEquals(0, TreeAsciiUtil.htmlLength(""));
assertEquals(0, TreeAsciiUtil.htmlLength(null));
}
@Test
public void testGenerateString() {
assertEquals("", TreeAsciiUtil.generateString("a", 0));
assertEquals("", TreeAsciiUtil.generateString("a", -1));
assertEquals("aaa", TreeAsciiUtil.generateString("a", 3));
assertEquals("&nbsp;&nbsp;", TreeAsciiUtil.generateString("&nbsp;", 2));
}
@Test
public void testConcatenateMultiLineSerialSimple() {
List<String> element1 = Arrays.asList("A", "B", "C");
List<String> element2 = Arrays.asList("D", "E", "F");
List<List<String>> elements = Arrays.asList(element1, element2);
List<String> result = TreeAsciiUtil.concatenateMultiLineSerial(" --- ", elements);
assertEquals(3, result.size());
assertEquals("A --- D", result.get(0));
assertEquals("B --- E", result.get(1));
assertEquals("C --- F", result.get(2));
}
@Test
public void testConcatenateMultiLineSerialDifferentHeights() {
List<String> element1 = Arrays.asList("A", "B", "C");
List<String> element2 = Arrays.asList("D");
List<List<String>> elements = Arrays.asList(element1, element2);
List<String> result = TreeAsciiUtil.concatenateMultiLineSerial(" --- ", elements);
assertEquals(3, result.size());
assertEquals("A --- &nbsp;", result.get(0));
assertEquals("B --- D", result.get(1));
assertEquals("C --- &nbsp;", result.get(2));
}
@Test
public void testConcatenateMultiLineParallelSimple() {
List<String> element1 = Arrays.asList("A", "B", "C");
List<String> element2 = Arrays.asList("D", "E", "F");
List<List<String>> elements = Arrays.asList(element1, element2);
List<String> result = TreeAsciiUtil.concatenateMultiLineParallel(elements);
assertEquals(7, result.size()); // 3 lines for element1 + 1 separator + 3 lines for element2
}
@Test
public void testConcatenateMultiLineParallelEmptyInput() {
assertEquals(0, TreeAsciiUtil.concatenateMultiLineParallel(Collections.emptyList()).size());
assertEquals(0, TreeAsciiUtil.concatenateMultiLineParallel(null).size());
}
@Test
public void testConcatenateMultiLineParallelSingleElement() {
List<String> element = Arrays.asList("A", "B");
List<List<String>> elements = Arrays.asList(element);
List<String> result = TreeAsciiUtil.concatenateMultiLineParallel(elements);
assertEquals(2, result.size());
// We just get back the original element
assertEquals(element.size(), result.size());
}
@Test
public void testConcatenateMultiLineParallelComplexStructure() {
// Create a more complex structure to test alignment
List<String> element1 = Arrays.asList("Short");
List<String> element2 = Arrays.asList("Line1", "Line2 Long", "Line3");
List<String> element3 = Arrays.asList("X", "Y", "Z", "W");
List<List<String>> elements = Arrays.asList(element1, element2, element3);
List<String> result = TreeAsciiUtil.concatenateMultiLineParallel(elements);
assertEquals(10, result.size()); // 1 + 1 + 3 + 1 + 4 = 10 lines
// Verify some characteristics that should be true
// The center line should have horizontal connections
assertTrue(result.get(4).contains(TreeAsciiUtil.BOXH));
// First element should have a right connection on its center line
assertTrue(result.get(0).contains(TreeAsciiUtil.BOXDR));
// Last element should have a left connection on its center line
assertTrue(result.get(6).contains(TreeAsciiUtil.BOXUR));
}
} }
@@ -161,7 +161,7 @@ public class GuitarDiagramAnalyzerTests {
Tree tree = new GuitarDiagramAnalyzer().constructTree(netlist); Tree tree = new GuitarDiagramAnalyzer().constructTree(netlist);
String s = tree.toString(); String s = tree.toString();
assertEquals( assertEquals(
"((Volume.1-2 + ((Tone1.2-3 + Cap1) || (Tone2.2-3 + Cap2) || Pickup1.North<- || Pickup2.North<-)) || Volume.2-3)", "((Volume.1-2 + (Pickup1.North<- || Pickup2.North<- || (Tone1.2-3 + Cap1) || (Tone2.2-3 + Cap2))) || Volume.2-3)",
s); s);
} }