|
6 | 6 | let uploadArea: HTMLElement; |
7 | 7 | let sequenceNumber = 101; |
8 | 8 | let driveNumber = 2; |
| 9 | + let cueStartNumber = 1; |
9 | 10 | let prefix = "1"; |
10 | 11 | let exportMode: "cues-and-timecode" | "cues-only" = "cues-and-timecode"; |
11 | 12 | let isDragOver = false; |
|
27 | 28 | }[]; |
28 | 29 |
|
29 | 30 | type FullData = { |
30 | | - uniqueSequences: ParsedData; |
| 31 | + uniqueCues: ParsedData; |
31 | 32 | repeatedSequences: RepeatedSequenceData; |
32 | 33 | filename: string; |
33 | 34 | }; |
|
102 | 103 | })) |
103 | 104 | .reverse(); |
104 | 105 |
|
105 | | - const uniqueSequences = data.filter((item) => !item.color); |
| 106 | + const uniqueCues = data.filter((item) => !item.color); |
106 | 107 | const repeatedSequenceList = data.filter((item) => item.color); |
107 | 108 |
|
108 | 109 | let sqCounter = sequenceNumber + 1; |
|
124 | 125 | processingStatus = "Generating XML files..."; |
125 | 126 | await new Promise((resolve) => setTimeout(resolve, 100)); |
126 | 127 |
|
127 | | - const xmlContent = generateMacroXML({ repeatedSequences, uniqueSequences, filename }); |
| 128 | + const xmlContent = generateMacroXML({ repeatedSequences, uniqueCues, filename }); |
128 | 129 | downloadContentWithName(xmlContent, `${filename}_macro.xml`); |
129 | 130 |
|
130 | 131 | if (exportMode === "cues-and-timecode") { |
131 | | - const timecodeContent = generateTimecodeXML({ repeatedSequences, uniqueSequences, filename }); |
| 132 | + const timecodeContent = generateTimecodeXML({ repeatedSequences, uniqueCues, filename }); |
132 | 133 | downloadContentWithName(timecodeContent, `${filename}_timecode.xml`); |
133 | 134 | } |
134 | 135 |
|
|
205 | 206 | .join(" "); |
206 | 207 | } |
207 | 208 |
|
208 | | - function generateMacroXML({ repeatedSequences, uniqueSequences, filename }: FullData): string { |
| 209 | + function generateMacroXML({ repeatedSequences, uniqueCues, filename }: FullData): string { |
209 | 210 | const obj = { |
210 | 211 | ...XML_HEADER, |
211 | 212 | GMA3: { |
|
216 | 217 | MacroLine: [ |
217 | 218 | // unique sequences |
218 | 219 | { |
219 | | - "@_Command": `Store Sequence ${sequenceNumber} Cue 1 thru ${uniqueSequences.length}`, |
| 220 | + "@_Command": `Store Sequence ${sequenceNumber} Cue ${cueStartNumber} thru ${uniqueCues.length + cueStartNumber - 1}`, |
220 | 221 | "@_Wait": "0.10", |
221 | 222 | }, |
222 | | - ...uniqueSequences.map((item, index) => ({ |
223 | | - "@_Command": `Label Sequence ${sequenceNumber} Cue ${index + 1} "${item.name}"`, |
| 223 | + ...uniqueCues.map((item, index) => ({ |
| 224 | + "@_Command": `Label Sequence ${sequenceNumber} Cue ${index + cueStartNumber} "${item.name}"`, |
224 | 225 | "@_Wait": "0.10", |
225 | 226 | })), |
226 | 227 | /// repeated sequences |
|
261 | 262 | return builder.build(obj); |
262 | 263 | } |
263 | 264 |
|
264 | | - function generateTimecodeXML({ uniqueSequences, repeatedSequences, filename }: FullData): string { |
| 265 | + function generateTimecodeXML({ uniqueCues, repeatedSequences, filename }: FullData): string { |
265 | 266 | const obj = { |
266 | 267 | ...XML_HEADER, |
267 | 268 | GMA3: { |
268 | 269 | "@_DataVersion": "1.4.0.2", |
| 270 | + // unique sequences |
269 | 271 | Timecode: { |
270 | 272 | "@_Name": filename, |
271 | 273 | "@_Guid": "00 00 00 00 3F 76 B7 04 32 0B 00 00 68 A1 4F AA", |
272 | 274 | "@_Cursor": "00.00", |
273 | | - "@_Duration": uniqueSequences.length > 0 ? (parseFloat(uniqueSequences[uniqueSequences.length - 1].start) + 1).toFixed(3) : "0.00", |
| 275 | + "@_Duration": uniqueCues.length > 0 ? (parseFloat(uniqueCues[uniqueCues.length - 1].start) + 1).toFixed(3) : "0.00", |
274 | 276 | "@_LoopCount": "0", |
275 | 277 | "@_TCSlot": "-1", |
276 | 278 | "@_SwitchOff": "Keep Playbacks", |
277 | | - "@_Goto": "as Go", |
| 279 | + // "@_Goto": "as Go", |
278 | 280 | "@_Timedisplayformat": "<10d11h23m45>", |
279 | 281 | "@_FrameReadout": "<Seconds>", |
280 | 282 | TrackGroup: [ |
|
295 | 297 | "@_Play": "", |
296 | 298 | "@_Rec": "", |
297 | 299 | CmdSubTrack: { |
298 | | - CmdEvent: uniqueSequences.map((item, index) => ({ |
299 | | - "@_Name": "Go+", |
| 300 | + CmdEvent: uniqueCues.map((item, index) => ({ |
| 301 | + "@_Name": "Goto", |
300 | 302 | "@_Time": item.start, |
301 | 303 | RealtimeCmd: { |
302 | 304 | "@_Type": "Key", |
|
318 | 320 | "@_Object": `ShowData.DataPools.Default.Sequences.Sequence ${sequenceNumber}`, |
319 | 321 | } |
320 | 322 | : {}), |
321 | | - "@_ExecToken": "Go+", |
| 323 | + "@_ExecToken": "Goto", |
322 | 324 | "@_ValCueDestination": `ShowData.DataPools.Default.Sequences.Sequence ${sequenceNumber}.${item.name}`, |
323 | 325 | }, |
324 | 326 | })), |
325 | 327 | }, |
326 | 328 | }, |
327 | 329 | }, |
328 | 330 | }, |
| 331 | + // repeated sequences |
329 | 332 | { |
330 | 333 | "@_Play": "", |
331 | 334 | "@_Rec": "", |
|
344 | 347 | "@_Rec": "", |
345 | 348 | CmdSubTrack: { |
346 | 349 | CmdEvent: item.timestamps.map((timestamp, index) => ({ |
347 | | - "@_Name": "Go+", |
| 350 | + "@_Name": "Goto", |
348 | 351 | "@_Time": timestamp, |
349 | 352 | RealtimeCmd: { |
350 | 353 | "@_Type": "Key", |
|
366 | 369 | "@_Object": `ShowData.DataPools.Default.Sequences.${item.name}`, |
367 | 370 | } |
368 | 371 | : {}), |
369 | | - "@_ExecToken": "Go+", |
| 372 | + "@_ExecToken": "Goto", |
370 | 373 | "@_ValCueDestination": `ShowData.DataPools.Default.Sequences.${item.name}.Cue 1`, |
371 | 374 | }, |
372 | 375 | })), |
|
466 | 469 | </div> |
467 | 470 | </div> |
468 | 471 |
|
| 472 | + <details class="advanced-mode-section section-card"> |
| 473 | + <summary class="section-summary"> |
| 474 | + <span class="label-text">Advanced</span> |
| 475 | + <span class="label-hint">Additional options</span> |
| 476 | + </summary> |
| 477 | + <div> |
| 478 | + <label for="drive-number" class="label"> |
| 479 | + <span class="label-text">Cue Start Number</span> |
| 480 | + <span class="label-hint">Starting number for cues (1-9999)</span> |
| 481 | + </label> |
| 482 | + <input id="drive-number" type="number" min="1" max="9999" step="1" bind:value={cueStartNumber} class="input" /> |
| 483 | + </div> |
| 484 | + </details> |
469 | 485 | <div class="export-mode-section"> |
470 | 486 | <div class="label"> |
471 | 487 | <span class="label-text">Export Mode</span> |
|
529 | 545 |
|
530 | 546 | <footer class="footer"> |
531 | 547 | <p>Made with ❤️ and Svelte - View <a target="_blank" href="https://github.com/hrueger/reaper2ma">Source on GitHub</a></p> |
532 | | - <p>© 2025 Hannes Rüger</p> |
| 548 | + <p>© 2025 - 2026 Hannes Rüger</p> |
533 | 549 | </footer> |
534 | 550 | </main> |
535 | 551 |
|
|
796 | 812 | flex-shrink: 0; |
797 | 813 | } |
798 | 814 |
|
799 | | - .export-mode-section { |
| 815 | + .export-mode-section, |
| 816 | + .advanced-mode-section { |
800 | 817 | margin-top: 1.5rem; |
801 | 818 | } |
802 | 819 |
|
| 820 | + .section-card { |
| 821 | + border: 1px solid var(--border-light); |
| 822 | + border-radius: 12px; |
| 823 | + padding: 1rem; |
| 824 | + background: color-mix(in srgb, var(--upload-bg) 82%, transparent); |
| 825 | + transition: |
| 826 | + border-color 0.2s ease, |
| 827 | + background 0.2s ease, |
| 828 | + box-shadow 0.2s ease; |
| 829 | + } |
| 830 | +
|
| 831 | + .section-card:hover { |
| 832 | + border-color: var(--border-hover); |
| 833 | + } |
| 834 | +
|
| 835 | + .section-summary { |
| 836 | + list-style: none; |
| 837 | + cursor: pointer; |
| 838 | + position: relative; |
| 839 | + padding-right: 2rem; |
| 840 | + } |
| 841 | +
|
| 842 | + .section-summary::-webkit-details-marker { |
| 843 | + display: none; |
| 844 | + } |
| 845 | +
|
| 846 | + .section-summary::after { |
| 847 | + content: ""; |
| 848 | + position: absolute; |
| 849 | + right: 0.5rem; |
| 850 | + top: 50%; |
| 851 | + width: 0.55rem; |
| 852 | + height: 0.55rem; |
| 853 | + border-right: 2px solid var(--text-secondary); |
| 854 | + border-bottom: 2px solid var(--text-secondary); |
| 855 | + transform: translateY(-65%) rotate(45deg); |
| 856 | + transition: transform 0.2s ease; |
| 857 | + } |
| 858 | +
|
| 859 | + .advanced-mode-section[open] .section-summary::after { |
| 860 | + transform: translateY(-25%) rotate(225deg); |
| 861 | + } |
| 862 | +
|
| 863 | + .advanced-mode-section > div { |
| 864 | + margin-top: 0.75rem; |
| 865 | + padding-top: 0.75rem; |
| 866 | + border-top: 1px solid var(--border-light); |
| 867 | + } |
| 868 | +
|
803 | 869 | .radio-group { |
804 | 870 | display: grid; |
805 | 871 | grid-template-columns: repeat(2, 1fr); |
|
948 | 1014 | flex-shrink: 0; |
949 | 1015 | } |
950 | 1016 |
|
951 | | - .note { |
952 | | - background: var(--note-bg); |
953 | | - padding: 1rem; |
954 | | - border-radius: 8px; |
955 | | - border-left: 4px solid var(--note-border); |
956 | | - margin: 0; |
957 | | - font-size: 0.9rem; |
958 | | - line-height: 1.5; |
959 | | - color: var(--text-primary); |
960 | | - transition: |
961 | | - background 0.3s ease, |
962 | | - border 0.3s ease; |
963 | | - } |
964 | | -
|
965 | 1017 | .footer { |
966 | 1018 | text-align: center; |
967 | 1019 | color: rgba(255, 255, 255, 0.8); |
|
0 commit comments