Skip to content

Commit 46cc5f7

Browse files
committed
feat: add support for modern CS7-style UDOs for both UDOs and Effects
1 parent fbc7672 commit 46cc5f7

24 files changed

+1789
-250
lines changed

ChangeLog.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,14 @@ information.
1919

2020
* Added cross-group layer selection and deletion support for audio and pattern layer panels, including coordinated shift-click selection and cleanup of empty groups.
2121

22+
* User-Defined Opcodes and Effects now support Csound 7 modern syntax alongside classic syntax:
23+
* UDOs and Effects have an explicit Classic/Modern style selector
24+
* Modern style uses `opcode name(inputArgs):outputSig` declaration and function-call invocation
25+
* Classic style retains traditional `opcode name, outTypes, inTypes` declaration with `xin`/`xout`
26+
* Bidirectional conversion between styles preserves input argument names and type annotations
27+
* Parsing and CSD import handle both single-line and multi-line modern declarations
28+
* Newly created UDOs and Effects default to application setting default (new setting in Program Settings for "Default UDO/Effects Style"); existing projects load as classic for backward compatibility
29+
2230
### Updated
2331

2432
* Introduced new Time Unit system encompassing time signatures, measures, new tempo ruler, and new time units:

blue-core/src/main/java/blue/mixer/Effect.java

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import blue.orchestra.blueSynthBuilder.BSBGraphicInterface;
2626
import blue.orchestra.blueSynthBuilder.StringChannel;
2727
import blue.udo.OpcodeList;
28+
import blue.udo.UDOStyle;
2829
import blue.udo.UserDefinedOpcode;
2930
import blue.utility.TextUtilities;
3031
import blue.utility.UDOUtilities;
@@ -39,6 +40,7 @@
3940

4041
public class Effect implements Automatable {
4142

43+
private UDOStyle style = UDOStyle.MODERN;
4244
private int numIns = 2;
4345
private int numOuts = 2;
4446
private BSBGraphicInterface graphicInterface;
@@ -64,6 +66,7 @@ private Effect(boolean init) {
6466
}
6567

6668
public Effect(Effect effect) {
69+
style = effect.style;
6770
numIns = effect.numIns;
6871
numOuts = effect.numOuts;
6972
code = effect.code;
@@ -82,12 +85,16 @@ public UserDefinedOpcode generateUDO(OpcodeList udoList) {
8285
opcodeList, udoList);
8386

8487
UserDefinedOpcode udo = new UserDefinedOpcode();
88+
udo.style = style;
8589

8690
BSBCompilationUnit bsbUnit = new BSBCompilationUnit();
8791
graphicInterface.setupForCompilation(bsbUnit);
8892

8993
StringBuilder buffer = new StringBuilder();
90-
buffer.append(getXinText()).append("\n");
94+
95+
if (style == UDOStyle.CLASSIC) {
96+
buffer.append(getXinText()).append("\n");
97+
}
9198

9299
buffer.append(bsbUnit.replaceBSBValues(code)).append("\n");
93100

@@ -102,21 +109,36 @@ public UserDefinedOpcode generateUDO(OpcodeList udoList) {
102109

103110
udo.codeBody = udoCode;
104111

105-
udo.inTypes = getSigTypes(numIns);
106-
udo.outTypes = getSigTypes(numOuts);
112+
if (style == UDOStyle.MODERN) {
113+
udo.inputArguments = getInputArguments();
114+
udo.outTypes = getCommaSeparatedSigTypes(numOuts);
115+
udo.inTypes = "";
116+
} else {
117+
udo.inputArguments = "";
118+
udo.outTypes = getSigTypes(numOuts);
119+
udo.inTypes = getSigTypes(numIns);
120+
}
107121

108122
return udo;
109123
}
110124

111125
public static Effect loadFromXML(Element data) throws Exception {
112126
Effect effect = new Effect(false);
127+
effect.style = UDOStyle.CLASSIC;
113128

114129
Elements nodes = data.getElements();
115130

116131
while (nodes.hasMoreElements()) {
117132
Element node = nodes.next();
118133
String nodeName = node.getName();
119134
switch (nodeName) {
135+
case "style" -> {
136+
try {
137+
effect.style = UDOStyle.valueOf(node.getTextString());
138+
} catch (IllegalArgumentException e) {
139+
effect.style = UDOStyle.CLASSIC;
140+
}
141+
}
120142
case "name" ->
121143
effect.setName(node.getTextString());
122144
case "enabled" ->
@@ -161,6 +183,7 @@ public static Effect loadFromXML(Element data) throws Exception {
161183
public Element saveAsXML() {
162184
Element retVal = new Element("effect");
163185

186+
retVal.addElement("style").setText(style.name());
164187
retVal.addElement("name").setText(name);
165188
retVal.addElement(XMLUtilities.writeBoolean("enabled", enabled));
166189
retVal.addElement(XMLUtilities.writeInt("numIns", numIns));
@@ -174,6 +197,17 @@ public Element saveAsXML() {
174197
return retVal;
175198
}
176199

200+
public UDOStyle getStyle() {
201+
return style;
202+
}
203+
204+
public void setStyle(UDOStyle style) {
205+
UDOStyle oldVal = this.style;
206+
this.style = style;
207+
firePropertyChangeEvent(new PropertyChangeEvent(this, "style",
208+
oldVal, style));
209+
}
210+
177211
public boolean isEnabled() {
178212
return enabled;
179213
}
@@ -291,6 +325,31 @@ public String toString() {
291325
// return editPanel;
292326
// }
293327

328+
private String getCommaSeparatedSigTypes(int num) {
329+
if (num == 0) {
330+
return "";
331+
}
332+
StringBuilder buffer = new StringBuilder();
333+
for (int i = 0; i < num; i++) {
334+
if (i > 0) {
335+
buffer.append(", ");
336+
}
337+
buffer.append("a");
338+
}
339+
return buffer.toString();
340+
}
341+
342+
private String getInputArguments() {
343+
StringBuilder buffer = new StringBuilder();
344+
for (int i = 0; i < numIns; i++) {
345+
if (i > 0) {
346+
buffer.append(", ");
347+
}
348+
buffer.append("ain").append(i + 1);
349+
}
350+
return buffer.toString();
351+
}
352+
294353
private String getXinText() {
295354
StringBuilder buffer = new StringBuilder();
296355

blue-core/src/main/java/blue/mixer/MixerNode.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import blue.CompileData;
44
import blue.automation.Parameter;
55
import blue.udo.OpcodeList;
6+
import blue.udo.UDOStyle;
67
import blue.udo.UserDefinedOpcode;
78
import blue.utility.MusicFunctions;
89
import blue.utility.NumberUtilities;
@@ -380,9 +381,15 @@ private static void applyEffects(EffectsChain chain, OpcodeList udos,
380381
udos.addOpcode(udo);
381382
}
382383

383-
buffer.append(signalChannels).append("\t");
384-
buffer.append(effectName).append("\t");
385-
buffer.append(signalChannels).append("\n");
384+
if (effect.getStyle() == UDOStyle.MODERN) {
385+
buffer.append(signalChannels).append(" = ");
386+
buffer.append(effectName).append("(");
387+
buffer.append(signalChannels).append(")\n");
388+
} else {
389+
buffer.append(signalChannels).append("\t");
390+
buffer.append(effectName).append("\t");
391+
buffer.append(signalChannels).append("\n");
392+
}
386393
}
387394
} else if (obj instanceof Send send) {
388395

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package blue.udo;
2+
3+
public enum UDOStyle {
4+
CLASSIC,
5+
MODERN
6+
}

blue-core/src/main/java/blue/udo/UserDefinedOpcode.java

Lines changed: 116 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
*/
2020
package blue.udo;
2121

22+
import blue.utility.UDOUtilities;
2223
import electric.xml.Element;
2324
import electric.xml.Elements;
2425

@@ -31,10 +32,14 @@ public class UserDefinedOpcode {
3132

3233
public transient String commentText = null;
3334

35+
public UDOStyle style = UDOStyle.CLASSIC;
36+
3437
public String outTypes = "";
3538

3639
public String inTypes = "";
3740

41+
public String inputArguments = "";
42+
3843
public String codeBody = "";
3944

4045
public String comments = "";
@@ -44,8 +49,10 @@ public UserDefinedOpcode() {
4449

4550
public UserDefinedOpcode(UserDefinedOpcode udo) {
4651
opcodeName = udo.opcodeName;
52+
style = udo.style;
4753
outTypes = udo.outTypes;
4854
inTypes = udo.inTypes;
55+
inputArguments = udo.inputArguments;
4956
codeBody = udo.codeBody;
5057
comments = udo.comments;
5158
}
@@ -64,35 +71,63 @@ public static UserDefinedOpcode loadFromXML(Element data) {
6471
val = "";
6572
}
6673
switch (node.getName()) {
74+
case "style" -> {
75+
try {
76+
retVal.style = UDOStyle.valueOf(val);
77+
} catch (IllegalArgumentException e) {
78+
retVal.style = UDOStyle.CLASSIC;
79+
}
80+
}
6781
case "opcodeName" ->
6882
retVal.opcodeName = val;
6983
case "outTypes" ->
7084
retVal.outTypes = val;
7185
case "inTypes" ->
7286
retVal.inTypes = val;
87+
case "inputArguments" ->
88+
retVal.inputArguments = val;
7389
case "codeBody" ->
7490
retVal.codeBody = val;
7591
case "comments" ->
7692
retVal.comments = val;
7793
}
7894
}
7995

96+
if (retVal.style == UDOStyle.MODERN) {
97+
retVal.inTypes = "";
98+
retVal.outTypes = UDOUtilities.normalizeModernOutTypes(retVal.outTypes);
99+
} else {
100+
retVal.inputArguments = "";
101+
retVal.outTypes = UDOUtilities.normalizeClassicOutTypes(retVal.outTypes);
102+
}
103+
80104
return retVal;
81105
}
82106

83107
public electric.xml.Element saveAsXML() {
84108
Element retVal = new Element("udo");
85109

110+
retVal.addElement("style").setText(style.name());
86111
retVal.addElement("opcodeName").setText(opcodeName);
87-
retVal.addElement("outTypes").setText(outTypes);
88-
retVal.addElement("inTypes").setText(inTypes);
112+
retVal.addElement("outTypes").setText(style == UDOStyle.MODERN
113+
? UDOUtilities.getModernOutTypesDisplay(outTypes)
114+
: outTypes);
115+
if (style == UDOStyle.MODERN) {
116+
retVal.addElement("inputArguments").setText(inputArguments);
117+
} else {
118+
retVal.addElement("inTypes").setText(inTypes);
119+
}
89120
retVal.addElement("codeBody").setText(codeBody);
90121
retVal.addElement("comments").setText(comments);
91122

92123
return retVal;
93124
}
94125

95126
public String generateCode() {
127+
if (style == UDOStyle.MODERN) {
128+
return generateModernCode();
129+
}
130+
96131
StringBuilder buffer = new StringBuilder();
97132

98133
buffer.append("\topcode ").append(opcodeName);
@@ -123,6 +158,67 @@ public String generateCode() {
123158

124159
}
125160

161+
private String generateModernCode() {
162+
StringBuilder buffer = new StringBuilder();
163+
164+
buffer.append("opcode ").append(opcodeName);
165+
buffer.append("(").append(inputArguments).append(")");
166+
buffer.append(":").append(UDOUtilities.getModernOutputSignature(outTypes));
167+
168+
if (commentText != null) {
169+
buffer.append(" ; ").append(commentText);
170+
}
171+
172+
String formattedCodeBody = indentModernCodeBody(codeBody);
173+
if (!formattedCodeBody.isEmpty()) {
174+
buffer.append("\n").append(formattedCodeBody);
175+
}
176+
177+
buffer.append("\nendop");
178+
179+
return buffer.toString();
180+
}
181+
182+
private String indentModernCodeBody(String source) {
183+
if (source == null || source.isEmpty()) {
184+
return "";
185+
}
186+
187+
String trimmedSource = trimTrailingLineBreaks(source);
188+
if (trimmedSource.isEmpty()) {
189+
return "";
190+
}
191+
192+
String[] lines = trimmedSource.split("\n", -1);
193+
StringBuilder buffer = new StringBuilder();
194+
195+
for (int i = 0; i < lines.length; i++) {
196+
if (i > 0) {
197+
buffer.append("\n");
198+
}
199+
200+
if (!lines[i].isEmpty()) {
201+
buffer.append(" ");
202+
}
203+
buffer.append(lines[i]);
204+
}
205+
206+
return buffer.toString();
207+
}
208+
209+
private String trimTrailingLineBreaks(String source) {
210+
int endIndex = source.length();
211+
while (endIndex > 0) {
212+
char currentChar = source.charAt(endIndex - 1);
213+
if (currentChar != '\n' && currentChar != '\r') {
214+
break;
215+
}
216+
endIndex--;
217+
}
218+
219+
return source.substring(0, endIndex);
220+
}
221+
126222
@Override
127223
public String toString() {
128224
return opcodeName;
@@ -158,8 +254,23 @@ public boolean isEquivalent(UserDefinedOpcode udo) {
158254
if (udo == null) {
159255
return false;
160256
}
161-
return (this.inTypes.equals(udo.inTypes)
162-
&& this.outTypes.equals(udo.outTypes) && this.codeBody
163-
.equals(udo.codeBody));
257+
String thisOutTypes = this.style == UDOStyle.MODERN
258+
? UDOUtilities.normalizeModernOutTypesForComparison(
259+
this.outTypes)
260+
: UDOUtilities.normalizeClassicOutTypes(this.outTypes);
261+
String otherOutTypes = udo.style == UDOStyle.MODERN
262+
? UDOUtilities.normalizeModernOutTypesForComparison(
263+
udo.outTypes)
264+
: UDOUtilities.normalizeClassicOutTypes(udo.outTypes);
265+
266+
if (this.style != udo.style || !thisOutTypes.equals(otherOutTypes)
267+
|| !this.codeBody.equals(udo.codeBody)) {
268+
return false;
269+
}
270+
271+
return switch (this.style) {
272+
case MODERN -> this.inputArguments.equals(udo.inputArguments);
273+
case CLASSIC -> this.inTypes.equals(udo.inTypes);
274+
};
164275
}
165276
}

0 commit comments

Comments
 (0)