Store and manage temporary binary data directly in memory using CE.SDK’s buffer API for dynamically generated content.
Buffers are in-memory containers for binary data referenced via buffer:// URIs. Unlike external files that require network or file I/O, buffers exist only during the current session and are not serialized when saving scenes. This makes them ideal for procedural audio, real-time image data, or streaming content that doesn’t need to persist beyond the current processing session.
This guide covers how to create and manage buffers, write and read binary data, assign buffers to block properties like audio sources, and handle transient resources when saving scenes.
Initializing the Engine#
We start by initializing the headless Creative Engine for server-side processing. The engine is initialized with an optional license key loaded from environment variables.
// Initialize the headless Creative Engineconst engine = await CreativeEngine.init({ // license: process.env.CESDK_LICENSE});Setting Up a Video Scene#
Since this example uses audio blocks, we create a video scene. Audio blocks require a timeline-based scene context.
// Create a video scene - required for audio blocksawait engine.scene.createVideo();Creating and Managing Buffers#
We use engine.editor.createBuffer() to allocate a new buffer and receive its URI. This URI follows the buffer:// scheme and uniquely identifies the buffer within the engine instance.
// Create a buffer and get its URIconst bufferUri = engine.editor.createBuffer();console.log('Buffer URI:', bufferUri);Buffers persist in memory until you explicitly destroy them with engine.editor.destroyBuffer() or the engine instance is disposed. For large buffers or long processing sessions, you should destroy buffers when they’re no longer needed to free memory.
Writing Data to Buffers#
To populate a buffer with binary data, we use engine.editor.setBufferData(). This method takes the buffer URI, an offset in bytes, and a Uint8Array containing the data to write.
In this example, we generate a 440 Hz sine wave as stereo PCM audio samples. We create a Float32Array for the sample values that will be converted to a valid audio format.
// Generate sine wave audio samplesconst sampleRate = 44100;const duration = 2; // 2 secondsconst frequency = 440; // A4 noteconst numChannels = 2; // Stereo
// Create Float32Array for audio samples (interleaved stereo)const numSamples = sampleRate * duration * numChannels;const samples = new Float32Array(numSamples);
// Generate a 440 Hz sine wavefor (let i = 0; i < numSamples; i += numChannels) { const sampleIndex = i / numChannels; const time = sampleIndex / sampleRate; const value = Math.sin(2 * Math.PI * frequency * time) * 0.5; // 50% amplitude
// Write to both left and right channels samples[i] = value; // Left channel samples[i + 1] = value; // Right channel}When using buffers for audio, the data must be in a recognized audio format like WAV. We convert the raw samples to a WAV file by adding the appropriate headers, then write the complete file to the buffer.
// Convert samples to WAV format and write to bufferconst wavData = createWavFile(samples, sampleRate, numChannels);engine.editor.setBufferData(bufferUri, 0, wavData);
// Verify the buffer lengthconst bufferLength = engine.editor.getBufferLength(bufferUri);console.log('Buffer length:', bufferLength, 'bytes');Reading Data from Buffers#
To read data back from a buffer, we use engine.editor.getBufferData() with the buffer URI, a starting offset, and the number of bytes to read. We first query the buffer length with engine.editor.getBufferLength() to determine how much data is available.
// Demonstrate reading buffer data backconst readData = engine.editor.getBufferData(bufferUri, 0, 100);console.log('First 100 bytes of buffer data:', readData);This returns a Uint8Array that you can convert back to other typed arrays as needed. Partial reads are supported—you can read any range within the buffer bounds.
Resizing Buffers#
You can change a buffer’s size at any time with engine.editor.setBufferLength(). Increasing the size allocates additional space, while decreasing it truncates the data. Here we demonstrate resizing with a separate demo buffer to avoid truncating our audio data.
// Demonstrate resizing a buffer with a separate demo bufferconst demoBuffer = engine.editor.createBuffer();engine.editor.setBufferData( demoBuffer, 0, new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]));
const demoLength = engine.editor.getBufferLength(demoBuffer);console.log('Demo buffer length before resize:', demoLength);
engine.editor.setBufferLength(demoBuffer, demoLength / 2);console.log( 'Demo buffer length after resize:', engine.editor.getBufferLength(demoBuffer));
engine.editor.destroyBuffer(demoBuffer);Keep in mind that truncating a buffer permanently discards data beyond the new length. Always query the current length first if you need to preserve the original size, or create a copy before resizing.
Assigning Buffers to Blocks#
Buffer URIs work like any other resource URI in CE.SDK. We assign them to block properties using engine.block.setString(). For audio blocks, we set the audio/fileURI property.
// Create an audio blockconst audioBlock = engine.block.create('audio');
// Assign the buffer URI to the audio blockengine.block.setString(audioBlock, 'audio/fileURI', bufferUri);
// Set audio duration to match the generated samplesengine.block.setDuration(audioBlock, duration);
// Append the audio block to the pageengine.block.appendChild(page, audioBlock);The same approach works for other resource properties:
- Audio blocks:
audio/fileURI - Image fills:
fill/image/imageFileURI - Video fills:
fill/video/fileURI
Any property that accepts a URI can reference a buffer.
Transient Resources and Scene Serialization#
Buffers are transient resources—the URI gets serialized when you save a scene, but the actual binary data does not persist. This means a saved scene will contain references to buffer:// URIs that won’t resolve when the scene is loaded again.
We use engine.editor.findAllTransientResources() to discover all transient resources in the current scene, including buffers. Each resource includes its URL and size in bytes.
// Find all transient resources (including our buffer)const transientResources = engine.editor.findAllTransientResources();console.log('Transient resources in scene:');for (const resource of transientResources) { console.log(` URL: ${resource.URL}, Size: ${resource.size} bytes`);}Note that engine.scene.saveToString() does NOT include buffer:// in its default allowed resource schemes, while engine.block.saveToString() does include it. You may need to configure the allowed schemes depending on your serialization needs.
Persisting Buffer Data#
To permanently save buffer content, you must extract the data, save it to persistent storage, then update the block references to point to the new URL. This example demonstrates the pattern by saving to a local file and using relocateResource() to update references—in production, you would upload to a CDN or cloud storage and use the CDN URL instead.
// Demonstrate persisting buffer data to a fileconst outputDir = './output';if (!existsSync(outputDir)) { mkdirSync(outputDir, { recursive: true });}
const bufferData = engine.editor.getBufferData(bufferUri, 0, bufferLength);const outputPath = `${outputDir}/generated-audio.wav`;writeFileSync(outputPath, Buffer.from(bufferData));console.log('Buffer data saved to:', outputPath);
// Update all references from buffer:// to the file path// In production, use a CDN URL instead of a local file pathconst absolutePath = `file://${process.cwd()}/${outputPath}`;engine.editor.relocateResource(bufferUri, absolutePath);console.log('Buffer relocated to:', absolutePath);We read the buffer data, write it to a local file, then use engine.editor.relocateResource() to update all references to the old buffer URI throughout the scene. After relocation, you can save the scene and the new persistent URLs will be serialized.
Troubleshooting#
Buffer data not appearing in exported scene
Buffers are transient and don’t persist with scene saves. Use findAllTransientResources() to identify buffers, then relocate them to persistent storage before exporting.
Memory usage growing unexpectedly
Call engine.editor.destroyBuffer() when buffers are no longer needed. Unlike external resources that can be garbage collected, buffers remain in memory until explicitly destroyed.
Data corruption when writing
Ensure the offset plus data length doesn’t exceed the intended buffer bounds. Resize the buffer first with setBufferLength() if you need more space.
Buffer URI not recognized by block
Verify the buffer was created in the same engine instance. Buffer URIs are not portable between different engine instances or sessions.