From 24ec10a039c11afb839dcba0d465093e02bedb6b Mon Sep 17 00:00:00 2001 From: soveda <161259864+soveda@users.noreply.github.com> Date: Fri, 17 Jul 2026 16:23:53 +0100 Subject: [PATCH] updated CozmicC1ZZL3 for more user friendly behaviour --- releases/84_CosmikC1zzl3/C1ZZL3.cpp | 121 ++++++++++-- releases/84_CosmikC1zzl3/CARD_README.md | 13 +- releases/84_CosmikC1zzl3/README.md | 75 ++++--- releases/84_CosmikC1zzl3/info.yaml | 32 +-- releases/84_CosmikC1zzl3/uf2/C1ZZL3.uf2 | Bin 276480 -> 278528 bytes releases/84_CosmikC1zzl3/uf2/SHA256SUMS.txt | 12 +- releases/84_CosmikC1zzl3/web/app.js | 12 +- releases/84_CosmikC1zzl3/web/import/app.js | 185 +++++++++++++++--- .../84_CosmikC1zzl3/web/import/index.html | 2 +- releases/84_CosmikC1zzl3/web/index.html | 4 +- 10 files changed, 349 insertions(+), 107 deletions(-) diff --git a/releases/84_CosmikC1zzl3/C1ZZL3.cpp b/releases/84_CosmikC1zzl3/C1ZZL3.cpp index aaaba585..4d9c617f 100644 --- a/releases/84_CosmikC1zzl3/C1ZZL3.cpp +++ b/releases/84_CosmikC1zzl3/C1ZZL3.cpp @@ -229,15 +229,22 @@ class C1ZZL3 : public ComputerCard updateTuringClockState(false); - bool pulse2Trigger = PulseIn2RisingEdge(); + bool pulse2GateHigh = PulseIn2(); + bool pulse2Trigger = pulse2GateHigh && !pulse2GateWasHigh; + pulse2GateWasHigh = pulse2GateHigh; + if (pulse2Trigger) { if (envelopePreset != (uint8_t)EnvelopePreset::Off) { syncOscillators(); - triggerEnvelope(); + triggerEnvelope(true, true); } } + else if (pulse2EnvelopeHolding && !pulse2GateHigh) + { + requestEnvelopeRelease(); + } outputSynthVoice(freq, pd, wave, ring, noiseAmt); @@ -322,14 +329,22 @@ class C1ZZL3 : public ComputerCard static constexpr uint8_t EnvelopePresetCount = 9; static constexpr uint8_t CustomEnvelopePreset = EnvelopePresetCount; static constexpr uint8_t CustomEnvelopeSlotCount = 8; + static constexpr uint8_t EnvelopeLoopStartStage = 2; + static constexpr uint8_t EnvelopeLoopEndStage = 5; + static constexpr uint16_t EnvelopeLoopLevelThreshold = 128; + static constexpr uint32_t EnvelopeLoopMinStageSamples = 960u; static constexpr uint32_t MinWebMidiEnvelopeSamples = 960u; static constexpr uint32_t MaxWebMidiStageSamples = 192000u; static constexpr uint32_t StartupSelectDelaySamples = 12000u; static constexpr uint32_t StartupSelectWindowSamples = 24000u; static constexpr uint32_t SaveMagic = 0x43315A33u; // C1Z3 static constexpr uint16_t SaveVersion = 3; - static constexpr int32_t OutputLowpassAlphaQ12 = 2458; // 7 kHz at 48 kHz. + static constexpr int32_t OutputLowpassAlphaQ12 = 2008; // ~5.2 kHz at 48 kHz. static constexpr int32_t OutputHighpassAlphaQ12 = 4075; // 40 Hz at 48 kHz. + static constexpr int32_t PdCompensationFloorQ12 = 3000; // Keep high-PD tones present but less inflated. + static constexpr int32_t HighPitchSofteningStart = 28000000; + static constexpr int32_t HighPitchSofteningRange = 42000000; + static constexpr int32_t HighPitchSofteningMaxQ12 = 1200; static constexpr uint32_t SaveFlashOffset = (PICO_FLASH_SIZE_BYTES - FLASH_SECTOR_SIZE) & ~(FLASH_SECTOR_SIZE - 1u); @@ -482,6 +497,7 @@ class C1ZZL3 : public ComputerCard osc1 = mix(osc1, ringSig, ringMix); int32_t ampScale = envelopeAmpScale(envelopeLevel); + ampScale = (ampScale * pdCompensationScale(pd)) >> 12; ampScale = (ampScale * updateSyncFade()) >> 12; osc1 = (osc1 * ampScale) >> 12; osc2 = (osc2 * ampScale) >> 12; @@ -537,6 +553,7 @@ class C1ZZL3 : public ComputerCard int32_t sine = getSine(renderPhase); int32_t target = morphWave(renderPhase, wave); + target = softenHighPitchTarget(sine, target, freq, pdCurve); return mix(sine, target, pdCurve); } @@ -558,7 +575,7 @@ class C1ZZL3 : public ComputerCard phase += (uint32_t)phaseJitter; } - void triggerEnvelope() + void triggerEnvelope(bool held = false, bool heldByPulse = false) { if (envelopePreset == (uint8_t)EnvelopePreset::Off) return; @@ -577,6 +594,9 @@ class C1ZZL3 : public ComputerCard pdEnvelopeStartLevel = pdStartLevel; pdEnvelopeLevel = pdStartLevel; envelopeActive = true; + envelopeHeld = held; + envelopeReleaseRequested = !held; + pulse2EnvelopeHolding = heldByPulse; } void triggerTuringEnvelope() @@ -591,24 +611,34 @@ class C1ZZL3 : public ComputerCard return 0; const EnvelopeProgram& program = envelopeProgram(); + bool loopEnabled = envelopeLoopEnabled(program); bool ampDone = updateEnvelopeRunner( program.amp, ampEnvelopeStage, ampEnvelopeSample, ampEnvelopeLevel, - ampEnvelopeStartLevel); + ampEnvelopeStartLevel, + envelopeHeld, + envelopeReleaseRequested, + loopEnabled); bool pdDone = updateEnvelopeRunner( program.pd, pdEnvelopeStage, pdEnvelopeSample, pdEnvelopeLevel, - pdEnvelopeStartLevel); + pdEnvelopeStartLevel, + envelopeHeld, + envelopeReleaseRequested, + loopEnabled); if (ampDone && pdDone) { envelopeActive = false; + envelopeHeld = false; + envelopeReleaseRequested = false; + pulse2EnvelopeHolding = false; if (midiNoteReleased) midiNoteActive = false; } @@ -670,7 +700,10 @@ class C1ZZL3 : public ComputerCard if (envelopePreset == (uint8_t)EnvelopePreset::Off || !envelopeActive) midiNoteActive = false; else + { midiNoteReleased = true; + requestEnvelopeRelease(); + } } return; } @@ -736,7 +769,7 @@ class C1ZZL3 : public ComputerCard if (envelopePreset != (uint8_t)EnvelopePreset::Off) { syncOscillators(); - triggerEnvelope(); + triggerEnvelope(true); } } @@ -1096,11 +1129,11 @@ class C1ZZL3 : public ComputerCard }}; static const EnvelopeProgram brass = {{ - {4095, 4800}, {3400, 30000}, {0, 18000}, {0, 1}, - {0, 1}, {0, 1}, {0, 1}, {0, 1} + {4095, 4800}, {3400, 30000}, {3200, 18000}, {3000, 18000}, + {3300, 18000}, {3100, 18000}, {1600, 18000}, {0, 24000} }, { - {1792, 4800}, {900, 30000}, {0, 18000}, {0, 1}, - {0, 1}, {0, 1}, {0, 1}, {0, 1} + {1792, 4800}, {900, 30000}, {1200, 18000}, {1000, 18000}, + {1300, 18000}, {900, 18000}, {500, 18000}, {0, 24000} }}; static const EnvelopeProgram strings = {{ @@ -1163,11 +1196,17 @@ class C1ZZL3 : public ComputerCard uint8_t& stage, uint32_t& sample, int32_t& level, - int32_t& startLevel) + int32_t& startLevel, + bool held, + bool releaseRequested, + bool loopEnabled) { if (stage >= 8) return true; + if (loopEnabled && stage > EnvelopeLoopEndStage && held && !releaseRequested) + stage = EnvelopeLoopStartStage; + uint32_t time = stages[stage].time; if (time == 0) time = 1; @@ -1182,11 +1221,38 @@ class C1ZZL3 : public ComputerCard stage++; sample = 0; startLevel = level; + + if (loopEnabled && stage > EnvelopeLoopEndStage && held && !releaseRequested) + stage = EnvelopeLoopStartStage; } return stage >= 8; } + bool envelopeLoopEnabled(const EnvelopeProgram& program) + { + return laneHasLoopBody(program.amp) || laneHasLoopBody(program.pd); + } + + bool laneHasLoopBody(const EnvelopeStage* stages) + { + for (uint32_t i = EnvelopeLoopStartStage; i <= EnvelopeLoopEndStage; ++i) + { + if (stages[i].level >= EnvelopeLoopLevelThreshold && + stages[i].time >= EnvelopeLoopMinStageSamples) + return true; + } + + return false; + } + + void requestEnvelopeRelease() + { + envelopeHeld = false; + envelopeReleaseRequested = true; + pulse2EnvelopeHolding = false; + } + inline int32_t morphWave(uint32_t phase, int32_t wave) { uint32_t scaled = ((uint32_t)wave * 7u); @@ -2112,6 +2178,33 @@ class C1ZZL3 : public ComputerCard return (ux * ux) >> 12; } + int32_t pdCompensationScale(int32_t pd) + { + int32_t pdCurve = responseCurve(pd); + int32_t reduction = (pdCurve * (4095 - PdCompensationFloorQ12)) >> 12; + return 4095 - reduction; + } + + int32_t softenHighPitchTarget( + int32_t sine, + int32_t target, + int32_t freq, + int32_t pdCurve) + { + int32_t pitchAmount = freq - HighPitchSofteningStart; + if (pitchAmount <= 0 || pdCurve <= 0) + return target; + + if (pitchAmount > HighPitchSofteningRange) + pitchAmount = HighPitchSofteningRange; + + int32_t pitchCurve = (pitchAmount << 12) / HighPitchSofteningRange; + int32_t soften = + (((pitchCurve * pdCurve) >> 12) * HighPitchSofteningMaxQ12) >> 12; + + return mix(target, sine, soften); + } + uint32_t smoothStep12(uint32_t x) { uint32_t x2 = (x * x) >> 12; @@ -2153,6 +2246,10 @@ class C1ZZL3 : public ComputerCard uint8_t ampEnvelopeStage = 8; uint8_t pdEnvelopeStage = 8; bool envelopeActive = false; + bool envelopeHeld = false; + bool envelopeReleaseRequested = true; + bool pulse2GateWasHigh = false; + bool pulse2EnvelopeHolding = false; uint8_t envelopePreset = 0; uint32_t startupSelectSamples = 0; uint32_t envelopeSelectHoldSamples = 0; diff --git a/releases/84_CosmikC1zzl3/CARD_README.md b/releases/84_CosmikC1zzl3/CARD_README.md index c25ab132..6b9c0e76 100644 --- a/releases/84_CosmikC1zzl3/CARD_README.md +++ b/releases/84_CosmikC1zzl3/CARD_README.md @@ -23,7 +23,9 @@ Put the switch in the middle. - `Audio/CV In 1` adds pitch at 1V/oct. - `CV In 1` adds phase distortion. - `CV In 2` adds wave control. -- `Pulse In 2` triggers the selected envelope and oscillator sync. +- `Pulse In 2` triggers the selected envelope, loops supported envelopes while + the gate is high, exits the loop when the gate falls, and syncs the + oscillators. The Turing CV and pulse outputs continue running in synth mode, so they can be used while playing the synth. @@ -78,7 +80,11 @@ MIDI CC controls on the selected input channel: Physical knobs use pickup after MIDI changes, so values do not jump until the knob is swept through the current setting. -## Web MIDI Editor +MIDI note-on behaves like a held gate. It triggers the selected envelope and +keeps loop-capable envelopes cycling until MIDI note-off lets the envelope +complete naturally. + +## Envelope Lab Hosted editor: @@ -94,7 +100,7 @@ choose the C1ZZL3 output, then: 3. Drag points on the graph to change both level and timing. 4. Watch the point numbers. Matching numbers mean the stages are stacked at the same spot. 5. Use the tables below the graph for exact values when you want precise edits. -6. Use the action buttons on the right when you want to send, save, read, or export. +6. Use the action buttons on the right when you want to send, save, read, or reset. Button quick reference: @@ -106,7 +112,6 @@ Button quick reference: - `Read Envelopes from Card` to load saved card envelopes into the editor. - `Read Settings from Card` to load the current card settings into the editor. - `Send Settings` to send performance settings until reset. -- `Export JSON` to download all editor presets. - `Reset Preset` to restore the selected preset to its factory value. The card can save up to eight custom envelopes. Factory presets are not diff --git a/releases/84_CosmikC1zzl3/README.md b/releases/84_CosmikC1zzl3/README.md index afa4f466..ab9a4629 100644 --- a/releases/84_CosmikC1zzl3/README.md +++ b/releases/84_CosmikC1zzl3/README.md @@ -24,24 +24,25 @@ uf2/C1ZZL3.uf2 Checksum: ```text -12c356ad75d6d25fccab5060dbb057f1e3ed86169c2429a798c66f835825ed61 +254fa4e525b8c66201a4bfe944b4cdcd2909a61caff440e383ef23d9b3e1890a ``` -This is hardware-tested production release 1.1, promoted on 2026-07-06. +This is hardware-tested production release 1.3, promoted on 2026-07-17. -Release 1.1 works with Envelope Lab and C1ZZL3 Import Lab and includes Web +Release 1.3 works with Envelope Lab and C1ZZL3 Import Lab and includes Web MIDI PD, detune, eight waveform families, card-to-editor envelope readback, -and browser CZ patch import handoff. +browser CZ patch import handoff, gate-held envelope looping with natural +completion on gate/note release, corrected CZ DCW-to-PD and DCA-to-amplitude +import mapping, and the audio smoothing pass for high-PD tones. ## Current Stable Feature Set - Phase-distortion synth voice. - Factory envelopes plus eight protected custom envelope slots. - Web MIDI envelope editor. -- Hosted CZ patch import workflow. - USB MIDI device mode for DAW/browser use. - USB MIDI host mode for class-compliant controllers. -- MIDI notes with envelope triggering. +- MIDI notes with gate-held envelope sustain/release. - MIDI CC control with knob pickup handoff. - Turing machine audio, CV, pulse, and optional MIDI note output. - Turing CV and pulse outputs continue running in synth mode. @@ -57,7 +58,7 @@ Switch middle: synth mode. - Y: waveform - `CV In 1`: phase-distortion modulation - `CV In 2`: waveform modulation -- `Pulse In 2`: envelope trigger and oscillator sync +- `Pulse In 2`: gate-held envelope trigger/release and oscillator sync Switch down from middle: performance edit and save. @@ -120,10 +121,20 @@ Hosted import lab: https://tomwhitwell.github.io/Workshop_Computer/programs/84-cosmikc1zzl3/web/import/index.html ``` -The Import Lab is included at `web/import/index.html`. It decodes Casio CZ -`.syx` patches into C1ZZL3 drafts, then hands the result to Envelope Lab for -final editing and sending. The navigation buttons at the top of both pages -switch between the two labs. +Local import lab from this release folder: + +```sh +python3 -m http.server 5174 --directory web/import +``` + +Open: + +```text +http://localhost:5174 +``` + +Use this page to decode Casio CZ `.syx` patches into C1ZZL3 drafts, then open +the result in Envelope Lab for final editing and sending. Current Import Lab features: @@ -131,6 +142,11 @@ Current Import Lab features: - Larger `C1ZZL3 Import Lab` header and a clearer guided import workflow. - Drag-and-drop or file-picker import for Casio CZ `.syx` files. - Browser-side validation, patch summary, decoded data, and draft mapping. +- CZ frame awareness for common patch-send SysEx files, including command, + location, channel, selected data offset, and payload candidates. +- Correct first-pass envelope assignment: CZ DCW maps to C1ZZL3 phase + distortion, CZ DCA maps to C1ZZL3 amplitude, and CZ pitch is decoded but not + assigned. - Draft handoff into Envelope Lab in a new tab for final editing and card send. - Separate import page so CZ translation and envelope editing stay distinct. @@ -169,27 +185,23 @@ Use `Save Envelope` to retain an envelope in flash. To make the current performance settings the startup baseline, move the hardware switch from middle to down and hold it until the card confirms the save. -The card can save up to eight custom envelopes. The browser can retain -additional local drafts. Factory presets are not overwritten. +The card can save up to eight custom envelopes. The browser can retain additional +local drafts. Factory presets are not overwritten. Custom presets are labelled `Local only`, `Saved - slot N`, or `Changed - slot N`. -Envelope readback confirms which custom slots are occupied and verifies saves -and deletions when supported by the firmware. +Envelope readback confirms which custom slots are occupied and verifies saves and +deletions when supported by the firmware. -`Play` loops a browser preview of the envelope. -`Stop` stops the browser preview only. It does not send a stop command to the -hardware. -`Bounce` is the reset preset because it shows the envelope shape clearly. +Envelope behaviour: -## Mobile Web MIDI Notes - -- Mobile browser support is less consistent than desktop support. -- If `MIDI` connects but no output appears, open `Developer tools` in the editor - and check `MIDI Ports Seen By Browser`. -- Some mobile browsers report MIDI access but expose ports in a non-standard - way. The editor tries several detection paths, but browser/device support can - still vary. -- If ports still do not appear, reconnect the device, reload the page, and try - another USB adapter, hub, or browser. +- Pulse In 2 and MIDI note-on trigger the selected envelope and sync the + oscillators. +- While the gate or MIDI note is held, loop-capable envelopes cycle their middle + stages. +- A short trigger runs the envelope through to completion. +- Pulse In 2 gate-off or MIDI note-off exits the loop and lets the envelope + complete naturally from its current point. +- Turing-triggered envelopes continue to run through without waiting for a gate + release. ## Build @@ -207,8 +219,8 @@ build/C1ZZL3.uf2 The production source build currently reports: ```text -FLASH: 138160 B -RAM: 146500 B +FLASH: 139072 B +RAM: 147412 B ``` ## Stability Notes @@ -225,3 +237,4 @@ Possible future optimisation notes are kept in: ```text FUTURE_NOTES.md ``` + diff --git a/releases/84_CosmikC1zzl3/info.yaml b/releases/84_CosmikC1zzl3/info.yaml index afef94a2..eb5c2cd8 100644 --- a/releases/84_CosmikC1zzl3/info.yaml +++ b/releases/84_CosmikC1zzl3/info.yaml @@ -1,16 +1,18 @@ id: 84_CosmikC1zzl3 title: Cosmik C1ZZL3 draft: false -release: 84 / 1.1 -date: 2026-06-24 +release: 84 / 1.3 +date: 2026-07-17 summary: >- - Stable phase-distortion synthesiser and Turing machine firmware with Web MIDI - envelope readback, PD, detune, eight waveform families, hosted CZ patch - import, USB MIDI device/host operation, and optional Turing MIDI output. + Stable phase-distortion synthesiser and Turing machine firmware with gate-held + looping Web MIDI envelopes, envelope readback, PD, detune, eight waveform + families, hosted CZ patch import, USB MIDI device/host operation, and optional + Turing MIDI output. description: >- - Stable phase-distortion synthesiser and Turing machine firmware with Web MIDI - envelope readback, PD, detune, eight waveform families, hosted CZ patch - import, USB MIDI device/host operation, and optional Turing MIDI output. + Stable phase-distortion synthesiser and Turing machine firmware with gate-held + looping Web MIDI envelopes, envelope readback, PD, detune, eight waveform + families, hosted CZ patch import, USB MIDI device/host operation, and optional + Turing MIDI output. panel: controls: main: @@ -61,8 +63,8 @@ panel: Envelope Trig description: >- - Triggers selected envelope and oscillator sync in synth mode when preset is - active + Triggers selected envelope and oscillator sync in synth mode; held gates loop + supported envelopes and gate-off lets them complete source: info.yaml outputs: audio_out_l: @@ -139,18 +141,20 @@ download_url: >- metadata: creator: Adrian Vos language: C++ (Pico SDK) - version: 1.1 + version: 1.3 status: Released license: MIT editor_url: https://tomwhitwell.github.io/Workshop_Computer/programs/84-cosmikc1zzl3/web/index.html editor_note: Configure this card in Envelope Lab; use web/import/index.html for CZ patch translation. documentation: intro: > - Phase-distortion synth card with Turing machine mode, USB MIDI, and browser - editor tools. + Phase-distortion synth card with Turing machine mode, USB MIDI, browser + editor tools, CZ Import Lab handoff, gate-held envelope looping, natural + trigger completion, and audio smoothing for high-PD tones. Switch middle is PD synth mode (Main = pitch, X = phase distortion, Y = waveform - morph). + morph). Pulse In 2 and MIDI note-on loop supported envelopes while held; + gate/note-off lets the envelope complete naturally. Hold switch down from middle for performance edit (detune, ring modulation, noise/grit) and save. diff --git a/releases/84_CosmikC1zzl3/uf2/C1ZZL3.uf2 b/releases/84_CosmikC1zzl3/uf2/C1ZZL3.uf2 index 2f94d2877f66f008289760bbd8ba2985a0902ee5..dc9ab74ab9c58d082585b878e0d47f0b0493daec 100644 GIT binary patch delta 55156 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 Q-}9YszZJ>9e|g4#1N{Bra{vGU delta 52208 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 Vh%LG6qV!F-rEqeo b >= 0 && b <= 15); +} + +function commandName(command) { + if (command === CZ_SEND_PATCH_COMMAND) return "send patch data"; + if (command === CZ_REQUEST_PATCH_COMMAND) return "request patch data"; + return `unknown command 0x${command.toString(16).padStart(2, "0")}`; +} + +function locationName(location) { + if (location === 0x60) return "current sound / edit buffer"; + if (location >= 0x20 && location <= 0x2f) return `internal memory ${location - 0x1f}`; + if (location >= 0x00 && location <= 0x0f) return `preset memory ${location + 1}`; + if (location >= 0x40 && location <= 0x4f) return `cartridge memory ${location - 0x3f}`; + return `location 0x${location.toString(16).padStart(2, "0")}`; +} + +function decodedLengthSupported(length) { + return length >= MIN_DECODED_PATCH_BYTES && length <= MAX_DECODED_PATCH_BYTES; +} + +function analyzePayloadCandidate(frame, offset, label) { + const payload = frame.slice(offset, -1); + const nibblePayload = isNibblePayload(payload); + const evenNibbleCount = payload.length % 2 === 0; + const decodedBytes = nibblePayload ? unpackNibbles(payload) : []; + const decodedLengthOk = decodedLengthSupported(decodedBytes.length); + + return { + offset, + label, + payload, + nibblePayload, + evenNibbleCount, + decodedBytes, + decodedLengthOk, + supported: nibblePayload && evenNibbleCount && decodedLengthOk + }; +} + +function analyzeCzFrame(frame) { + const manufacturer = frame[1]; + const familyA = frame[2]; + const familyB = frame[3]; + const channelByte = frame[4]; + const command = frame[5] ?? 0; + const location = frame[6] ?? 0; + const looksCasio = manufacturer === 0x44; + const looksCz = + looksCasio && + familyA === 0x00 && + familyB === 0x00 && + channelByte >= 0x70 && + channelByte <= 0x7f; + const channel = looksCz ? (channelByte - 0x70) + 1 : null; + const candidates = []; + + if (frame.length > 9) { + candidates.push(analyzePayloadCandidate(frame, 9, "CZ-101/CZ-1000 style data after two send bytes")); + } + if (frame.length > 7) { + candidates.push(analyzePayloadCandidate(frame, 7, "short CZ send data after location byte")); + } + + const preferred = + candidates.find((candidate) => command === CZ_SEND_PATCH_COMMAND && candidate.offset === 9 && candidate.supported) || + candidates.find((candidate) => candidate.supported) || + candidates[0] || + analyzePayloadCandidate(frame, 7, "fallback data after location byte"); + + return { + manufacturer, + familyA, + familyB, + channelByte, + channel, + command, + location, + looksCasio, + looksCz, + commandSupported: command === CZ_SEND_PATCH_COMMAND, + candidates, + preferred, + headerBytes: Array.from(frame.slice(0, Math.min(frame.length, preferred.offset))) + }; +} + +function summarizeDecodedBytes(bytes, czInfo) { const header = bytes.slice(0, 16); const tail = bytes.slice(-16); - return [ + const lines = [ `Decoded bytes: ${bytes.length}`, `Head: ${toHex(header, 16)}`, `Tail: ${toHex(tail, 16)}`, `Non-zero count: ${bytes.filter((b) => b !== 0).length}`, - ].join("\n"); + ]; + + if (czInfo) { + lines.unshift( + `CZ header bytes: ${toHex(czInfo.headerBytes, czInfo.headerBytes.length)}`, + `CZ channel: ${czInfo.channel ?? "not detected"}`, + `CZ command: ${commandName(czInfo.command)}`, + `CZ location: ${locationName(czInfo.location)}`, + `Patch data offset: ${czInfo.preferred.offset} (${czInfo.preferred.label})` + ); + + lines.push( + "", + "Payload candidates:", + ...czInfo.candidates.map((candidate) => { + const status = candidate.supported ? "usable" : "not selected"; + return `- offset ${candidate.offset}: ${candidate.payload.length} payload bytes, ${candidate.decodedBytes.length} decoded bytes, ${status}`; + }) + ); + } + + return lines.join("\n"); } function clamp(value, min, max) { @@ -166,17 +281,23 @@ function buildDraftPreset(decodedBytes, patchName) { .trim() || "Imported CZ patch"; const wave = classifyWave(decodedBytes); - const amp = buildStagesFromBytes(decodedBytes, 0, 0, 140, 14000); - const pd = buildStagesFromBytes(decodedBytes, 16, 0, 120, 18000); - const detune = clamp(Math.round((decodedBytes[32] ?? 128) / 255 * 4095), 0, 4095); - const ring = clamp(Math.round((decodedBytes[33] ?? 0) / 255 * 1200), 0, 4095); - const noise = clamp(Math.round((decodedBytes[34] ?? 0) / 255 * 700), 0, 4095); + const pitchEnvelope = buildStagesFromBytes(decodedBytes, 0, 0, 120, 18000); + const dcwEnvelope = buildStagesFromBytes(decodedBytes, 16, 0, 120, 18000); + const ampEnvelope = buildStagesFromBytes(decodedBytes, 32, 0, 140, 14000); + const detune = clamp(Math.round((decodedBytes[48] ?? 128) / 255 * 4095), 0, 4095); + const ring = clamp(Math.round((decodedBytes[49] ?? 0) / 255 * 1200), 0, 4095); + const noise = clamp(Math.round((decodedBytes[50] ?? 0) / 255 * 700), 0, 4095); return { name: `${baseName} draft`, wave, - amp, - pd, + amp: ampEnvelope, + pd: dcwEnvelope, + sourceEnvelopes: { + pitch: pitchEnvelope, + dcw: dcwEnvelope, + amp: ampEnvelope + }, performance: { detune, ring, noise }, confidence: "medium" }; @@ -201,7 +322,7 @@ function renderDraft(draft) { } el.decodedPatchBox.textContent = `${draft.name} decoded and unpacked into a draft preset.`; - el.mappedDraftBox.textContent = `8-wave family ${draft.wave.label}, translated amplitude envelope, and translated phase distortion envelope are ready for review.`; + el.mappedDraftBox.textContent = `8-wave family ${draft.wave.label}, CZ DCW envelope -> C1ZZL3 phase distortion, and CZ amplitude envelope -> C1ZZL3 amplitude are ready for review. Pitch envelope is decoded but not assigned yet.`; el.confidenceBox.textContent = draft.confidence; el.supportedOutputBox.textContent = "Envelope Lab draft handoff"; el.draftNameBox.textContent = `${draft.name} (${draft.confidence} confidence)`; @@ -270,24 +391,25 @@ function decodePatch(buffer, fileName) { }; } - const manufacturer = frame[1]; - const deviceId = frame[2]; - const modelBytes = Array.from(frame.slice(3, 7)); - const payload = frame.slice(7, -1); - const nibblePayload = Array.from(payload).every((b) => b >= 0 && b <= 15); - const decodedBytes = nibblePayload ? unpackNibbles(payload) : []; - const evenNibbleCount = payload.length % 2 === 0; - const decodedLengthOk = - decodedBytes.length >= MIN_DECODED_PATCH_BYTES && - decodedBytes.length <= MAX_DECODED_PATCH_BYTES; - const looksCasio = manufacturer === 0x44; - const supportedCandidate = looksCasio && nibblePayload && evenNibbleCount && decodedLengthOk; + const czInfo = analyzeCzFrame(frame); + const manufacturer = czInfo.manufacturer; + const payload = czInfo.preferred.payload; + const nibblePayload = czInfo.preferred.nibblePayload; + const decodedBytes = czInfo.preferred.decodedBytes; + const evenNibbleCount = czInfo.preferred.evenNibbleCount; + const decodedLengthOk = czInfo.preferred.decodedLengthOk; + const supportedCandidate = + czInfo.looksCz && + czInfo.commandSupported && + czInfo.preferred.supported; const patchName = fileName.replace(/\.(syx|mid|sysex)$/i, ""); const confidence = supportedCandidate ? "medium" : "low"; const draft = supportedCandidate ? buildDraftPreset(decodedBytes, patchName) : null; const validationReasons = []; - if (!looksCasio) validationReasons.push("manufacturer was not Casio `0x44`"); + if (!czInfo.looksCasio) validationReasons.push("manufacturer was not Casio `0x44`"); + if (czInfo.looksCasio && !czInfo.looksCz) validationReasons.push("header did not match the common CZ family form `F0 44 00 00 7n`"); + if (czInfo.looksCz && !czInfo.commandSupported) validationReasons.push(`command was ${commandName(czInfo.command)}, not send patch data`); if (!nibblePayload) validationReasons.push("payload was not nibble-packed"); if (nibblePayload && !evenNibbleCount) validationReasons.push("nibble payload length was uneven"); if (nibblePayload && !decodedLengthOk) { @@ -298,17 +420,20 @@ function decodePatch(buffer, fileName) { ok: supportedCandidate, tone: supportedCandidate ? "ok" : "warn", validation: supportedCandidate - ? `Casio-style nibble-packed patch candidate found (${frame.length} bytes, device id ${deviceId}).` + ? `Casio CZ ${commandName(czInfo.command)} frame found (${frame.length} bytes, ${locationName(czInfo.location)}, data offset ${czInfo.preferred.offset}).` : `This file does not look like a supported Casio CZ single-patch draft candidate: ${validationReasons.join("; ")}.`, patchType: supportedCandidate - ? "Casio CZ-style single-patch draft candidate" + ? "Casio CZ single-patch send frame" : "Unsupported or different synth family", summary: [ `File: ${fileName}`, `Frame length: ${frame.length} bytes`, `Manufacturer: 0x${manufacturer.toString(16).padStart(2, "0")}`, - `Device ID: ${deviceId}`, - `Model bytes: ${modelBytes.map((b) => `0x${b.toString(16).padStart(2, "0")}`).join(" ")}`, + `CZ family bytes: 0x${czInfo.familyA.toString(16).padStart(2, "0")} 0x${czInfo.familyB.toString(16).padStart(2, "0")}`, + `MIDI channel byte: 0x${czInfo.channelByte.toString(16).padStart(2, "0")}${czInfo.channel ? ` (channel ${czInfo.channel})` : ""}`, + `Command: ${commandName(czInfo.command)}`, + `Location: ${locationName(czInfo.location)}`, + `Selected data offset: ${czInfo.preferred.offset}`, `Payload bytes: ${payload.length}`, `Nibble-packed payload: ${nibblePayload ? "yes" : "no"}`, `Even nibble count: ${evenNibbleCount ? "yes" : "no"}`, @@ -317,7 +442,7 @@ function decodePatch(buffer, fileName) { `Confidence: ${confidence}` ].join("\n"), decoded: nibblePayload - ? summarizeDecodedBytes(decodedBytes) + ? summarizeDecodedBytes(decodedBytes, czInfo) : [ "Payload was not nibble-packed.", `Raw frame head: ${toHex(frame, 24)}`, diff --git a/releases/84_CosmikC1zzl3/web/import/index.html b/releases/84_CosmikC1zzl3/web/import/index.html index 077b1cdf..8a5cc9b2 100644 --- a/releases/84_CosmikC1zzl3/web/import/index.html +++ b/releases/84_CosmikC1zzl3/web/import/index.html @@ -31,7 +31,7 @@

C1ZZL3 Import Lab

diff --git a/releases/84_CosmikC1zzl3/web/index.html b/releases/84_CosmikC1zzl3/web/index.html index 7296178a..b8dc9383 100644 --- a/releases/84_CosmikC1zzl3/web/index.html +++ b/releases/84_CosmikC1zzl3/web/index.html @@ -15,7 +15,7 @@

Envelope Lab

Online - Import Lab + Import Lab @@ -211,6 +211,6 @@

Event Log

- +