Skip to main content

3 Writing Samples

https://toem.io/resources/impulse-api/writing-samples/

This guide provides a comprehensive overview of writing signal data within the impulse framework. The framework offers a rich hierarchy of writer interfaces designed for efficient and flexible data generation, ranging from simple value insertion to complex structural composition.

Introduction to Writing Signals

Writing signals in the impulse framework provides a way to generate, transform, or synthesize signal data for analysis and visualization. Whether you're creating test data, converting from other formats, or generating signals programmatically, the writer interfaces make it possible to build signals that integrate seamlessly with the impulse ecosystem.

The writing process follows a specific lifecycle that ensures data integrity and performance. Unlike traditional file formats where data is simply appended, impulse signals maintain domain-specific information about sample positioning, types, and relationships—ensuring that generated signals retain the rich metadata needed for meaningful analysis.

Each writer interface is specialized for a particular signal type, providing type-safe methods that handle the specifics of different data representations. This allows you to focus on the signal content rather than low-level encoding details.

Core Writing Interfaces

  • ISamplesWriter: The foundational interface for all signal writing operations, providing methods for opening/closing writers, managing samples, setting up enumerations, and controlling signal characteristics.
  • INumberSamplesWriter: The base interface for all numeric signal writers, extending the core writer with common numeric value handling.
  • IIntegerSamplesWriter: Specialized for writing integer values with various precision levels (int, long, BigInteger).
  • IFloatSamplesWriter: Specialized for writing floating-point values (float, double, BigDecimal).
  • ILogicSamplesWriter: Focused on writing digital logic states, supporting various representations of binary values.
  • IEventSamplesWriter: Designed for writing discrete event data, often using enumeration values.
  • ITextSamplesWriter: Used for writing string values and text arrays.
  • IBinarySamplesWriter: Provides methods for writing raw binary data as byte arrays.
  • IStructSamplesWriter: Enables writing complex structured data with named members and hierarchical relationships.

Understanding Writer Fundamentals

Writing signals in the impulse framework follows a structured approach with specific patterns and conventions. The framework provides a comprehensive set of writer interfaces, each tailored to a particular signal type, ensuring type safety and proper encoding of data. Before diving into specific writer types, it's essential to understand the fundamental concepts that apply to all writers.

The core concepts of signal writing include:

  • Writer Lifecycle: All writers follow a consistent open-write-close pattern
  • Position Management: Every sample is written at a specific domain position
  • Process Types: Signals can be discrete (event-based) or continuous (sample-rate based)
  • Tagging: Samples can be marked as special for visualization or analysis
  • Grouping: Related samples can be organized into logical transactions or groups

Understanding these fundamentals will help you write effective, efficient code for generating signals, regardless of the specific data type you're working with.

Writer Lifecycle

Writers have a well-defined lifecycle that must be followed for proper operation:

// 1. Obtain a writer for a signal
IIntegerSamplesWriter writer = (IIntegerSamplesWriter) getWriter(integerSignal);

// 2. Open the writer with a starting position
writer.open(0); // Start at position 0

// 3. Write samples
writer.write(100, false, 42);
writer.write(200, false, 84);

// 4. Close the writer at the final position
writer.close(300);

Method Descriptions:

Method Description Parameters Return Value Notes
open(long) Opens a writer for a discrete signal long - Starting position void Must be called before writing any samples
write(long, boolean, int) Writes an integer value at specified position long - Position, boolean - Tag flag, int - Value void Position must be non-decreasing
close(long) Closes the writer at final position long - End position void Finalizes the signal and makes it available for reading

Position and Time Management

All write operations require a position parameter, which represents the domain position as a multiple of the signal's domain base:

// With TimeBase.ns (nanoseconds), writing at 1 microsecond:
writer.write(1000, false, value);

// With TimeBase.ms (milliseconds), writing at 1 second:
writer.write(1000, false, value);

Method Descriptions:

Method Description Parameters Return Value Notes
write(long, boolean, T) Writes a value at specified position long - Position as domain base multiple, boolean - Tag flag, T - Value void Same position value means different real-world time depending on domain base

For continuous process signals, you must specify a rate when opening:

// Open with a rate of 10 units (e.g., 10 ns between samples)
writer.open(0, 10);

Method Descriptions:

Method Description Parameters Return Value Notes
open(long, long) Opens a writer for a continuous signal long - Starting position, long - Rate void Rate defines sampling interval between samples

Sample Tagging

Most write methods include a tag parameter to mark significant samples:

// Write a normal sample (no tag)
writer.write(100, false, 42);

// Write a tagged sample (will be displayed differently)
writer.write(200, true, 84);

// Some interfaces support integer tag levels (1-15)
writer.write(300, 2, 126); // Level 2 tag

Method Descriptions:

Method Description Parameters Return Value Notes
write(long, boolean, int) Writes value with boolean tag long - Position, boolean - Tag flag, int - Value void Simple tagged/untagged marking
write(long, int, int) Writes value with tag level long - Position, int - Tag level (0-15), int - Value void Supports multiple tag levels for importance

Writing Simple Data Types

Integer Values

The IIntegerSamplesWriter interface provides methods for writing whole-number values:

// Write an int value
writer.write(100, false, 42);

// Write a long value
writer.write(200, false, 123456789L);

// Write a BigInteger for arbitrary precision
writer.write(300, false, new BigInteger("12345678901234567890"));

// Write an integer array
writer.write(400, false, new int[]{1, 2, 3, 4});

// Helper methods for scripting
writer.writeInt(500, false, 42);
writer.writeLong(600, false, 123456789L);

Method Descriptions:

Method Description Parameters Return Value Notes
write(long, boolean, int) Writes an integer value long - Position, boolean - Tag flag, int - Value void For 32-bit integer values
write(long, boolean, long) Writes a long integer value long - Position, boolean - Tag flag, long - Value void For 64-bit integer values
write(long, boolean, BigInteger) Writes a BigInteger value long - Position, boolean - Tag flag, BigInteger - Value void For arbitrary-precision integers
write(long, boolean, int[]) Writes an integer array long - Position, boolean - Tag flag, int[] - Values void For multiple values as a single sample
writeInt(long, boolean, int) Convenience method for integers long - Position, boolean - Tag flag, int - Value void Helper for scripting environments
writeLong(long, boolean, long) Convenience method for longs long - Position, boolean - Tag flag, long - Value void Helper for scripting environments

Floating-Point Values

The IFloatSamplesWriter interface provides methods for writing decimal values:

// Write a float value
writer.write(100, false, 3.14f);

// Write a double value
writer.write(200, false, 2.71828);

// Write a BigDecimal for arbitrary precision
writer.write(300, false, new BigDecimal("3.141592653589793238462643383"));

// Convenience method without tagging
writer.write(400, 3.14);

// Helper methods for scripting
writer.writeFloat(500, false, 3.14f);
writer.writeDouble(600, false, 2.71828);

Method Descriptions:

Method Description Parameters Return Value Notes
write(long, boolean, float) Writes a float value long - Position, boolean - Tag flag, float - Value void For 32-bit floating-point values
write(long, boolean, double) Writes a double value long - Position, boolean - Tag flag, double - Value void For 64-bit floating-point values
write(long, boolean, BigDecimal) Writes a BigDecimal value long - Position, boolean - Tag flag, BigDecimal - Value void For arbitrary-precision decimals
write(long, double) Convenience method for double without tag long - Position, double - Value void Shorthand for write(pos, false, value)
writeFloat(long, boolean, float) Convenience method for float long - Position, boolean - Tag flag, float - Value void Helper for scripting environments
writeDouble(long, boolean, double) Convenience method for double long - Position, boolean - Tag flag, double - Value void Helper for scripting environments

Logic Values

The ILogicSamplesWriter interface provides methods for writing digital signal states:

// Write a single bit state (0, 1, X, Z, etc.)
writer.write(100, false, ILogicStates.STATE_1_BITS);

// Write a multi-bit value using a state array
byte[] states = new byte[]{ILogicStates.STATE_1_BITS, ILogicStates.STATE_0_BITS};
writer.write(200, false, ILogicStates.STATE_Z_BITS, states, 0, 2);

// Write using a string representation
writer.write(300, false, ILogicStates.STATE_0_BITS, "1XZ0");

// Write using a Logic object
writer.write(400, false, Logic.valueOf("10XZ"));

Method Descriptions:

Method Description Parameters Return Value Notes
write(long, boolean, byte) Writes a single logic state long - Position, boolean - Tag flag, byte - Logic state void For single-bit logic signals
write(long, boolean, byte, byte[], int, int) Writes multi-bit logic values long - Position, boolean - Tag flag, byte - Default state, byte[] - States array, int - Offset, int - Length void For multi-bit buses or vectors
write(long, boolean, byte, String) Writes logic states from string long - Position, boolean - Tag flag, byte - Default state, String - String representation void String format uses 0, 1, X, Z, etc.
write(long, boolean, Logic) Writes logic object long - Position, boolean - Tag flag, Logic - Logic value void For pre-constructed Logic objects

Event Values

The IEventSamplesWriter interface provides methods for writing discrete events:

// Write an event marker without data
writer.write(100, false);

// Write an event with an integer enumeration value
writer.write(200, false, 2); // Where 2 corresponds to a predefined enum value

// Write an event with a string enumeration label
writer.write(300, false, "RUNNING");

// Write an event with an Enumeration object
writer.write(400, false, new Enumeration(Enumeration.ENUM_GLOBAL, 2, "RUNNING"));

Method Descriptions:

Method Description Parameters Return Value Notes
write(long, boolean) Writes a simple event marker long - Position, boolean - Tag flag void For events without specific values
write(long, boolean, int) Writes an event with enum value long - Position, boolean - Tag flag, int - Enum value void References a predefined enumeration value
write(long, boolean, String) Writes an event with enum label long - Position, boolean - Tag flag, String - Enum label void Looks up enum by label name
write(long, boolean, Enumeration) Writes an event with Enumeration object long - Position, boolean - Tag flag, Enumeration - Enum object void For pre-constructed Enumeration objects

Text Values

The ITextSamplesWriter interface provides methods for writing textual data:

// Write a simple string
writer.write(100, false, "Hello World");

// Write an array of strings
writer.write(200, false, new String[]{"Hello", "World"});

// Helper methods for scripting
writer.writeString(300, false, "Hello World");
writer.writeStringArray(400, false, new String[]{"Hello", "World"});
writer.writeStringArgs(500, false, "Hello", "World");

Method Descriptions:

Method Description Parameters Return Value Notes
write(long, boolean, String) Writes a text value long - Position, boolean - Tag flag, String - Text value void For simple string values
write(long, boolean, String[]) Writes an array of strings long - Position, boolean - Tag flag, String[] - Text array void For multiple related strings
writeString(long, boolean, String) Convenience method for text long - Position, boolean - Tag flag, String - Text value void Helper for scripting environments
writeStringArray(long, boolean, String[]) Convenience method for string arrays long - Position, boolean - Tag flag, String[] - Text array void Helper for scripting environments
writeStringArgs(long, boolean, String...) Writes variable string arguments long - Position, boolean - Tag flag, String... - Text values void Converts varargs to string array

Binary Data

The IBinarySamplesWriter interface provides methods for writing raw binary content:

// Write a byte array
writer.write(100, false, new byte[]{0x01, 0x02, 0x03, 0x04});

// Write a subset of a byte array
byte[] largeArray = new byte[1024];
writer.write(200, false, largeArray, 0, 512); // Write the first 512 bytes

Method Descriptions:

Method Description Parameters Return Value Notes
write(long, boolean, byte[]) Writes a byte array long - Position, boolean - Tag flag, byte[] - Binary data void For complete binary data
write(long, boolean, byte[], int, int) Writes a portion of a byte array long - Position, boolean - Tag flag, byte[] - Binary data, int - Offset, int - Length void For partial binary data

Working with Complex Data Structures

The impulse framework provides robust support for complex data structures, allowing you to represent sophisticated data models in your signals. Complex structures are essential for many applications, such as:

  • Protocol analyzers that need to represent multi-field packet data
  • Transaction monitoring systems that track multi-step operations
  • Log analysis tools that capture structured event data with multiple attributes
  • Test systems that record complex measurements with multiple parameters

Complex data structures in impulse are represented using the Structure type, which can contain multiple named members of different types. These structures can be simple (flat collections of fields) or sophisticated (with nested arrays, hierarchical relationships, and varied data types). They can also be organized into groups to represent related sequences of events or transactions.

Working with these structures effectively requires understanding how to create, populate, and write structured data using the framework's helper methods and conventions.

Structured Data

The IStructSamplesWriter interface provides methods for writing compound data with named fields:

// Create an array to hold multiple struct members
StructMember[] members = writer.createMembers(2);

// Initialize the members with metadata and type information
writer.createMember(members, 0, "Command", "The command type", null, "important", 
                   ISample.DATA_TYPE_TEXT, 0, ISample.FORMAT_DEFAULT);
writer.createMember(members, 1, "Address", "Memory address", null, "address", 
                   ISample.DATA_TYPE_INTEGER, 0, ISample.FORMAT_HEXADECIMAL);

// Set the values
members[0].setValue("Write");
members[1].setValue(0x1000);

// Write a non-grouped structured sample
writer.write(100, false, members);

Method Descriptions:

Method Description Parameters Return Value Notes
createMembers(int) Creates a member array int - Number of members StructMember[] Helper for creating member arrays
createMember(StructMember[], int, String, String, String, String, int, int, int) Initializes a member StructMember[] - Array, int - Index, String - Name, String - Description, String - Unit/Icon, String - Tag, int - Data type, int - Scale, int - Format StructMember Comprehensive member initialization
setValue(Object) Sets member value Object - Value appropriate to the member type void Must match the member's data type
write(long, boolean, StructMember[]) Writes structured data long - Position, boolean - Tag flag, StructMember[] - Members array boolean For writing complex structured samples

Helper Methods for Structured Data

The IStructSamplesWriter interface provides additional methods to simplify creation of structured data:

// Create a hierarchical structure
StructMember parent = writer.createMember(null, "Packet", "Network packet", null, "packet", 
                      ISample.DATA_TYPE_STRUCT, 0, ISample.FORMAT_DEFAULT);

// Create a child member within the parent
StructMember child = writer.createMember(parent, "Header", "Packet header", null, "header", 
                     ISample.DATA_TYPE_STRUCT, 0, ISample.FORMAT_DEFAULT);

// Set values and write
parent.setValue(null); // Parent is just a container
child.setValue("TCP"); // Child has actual data
writer.write(100, false, new StructMember[] { parent });

Method Descriptions:

Method Description Parameters Return Value Notes
createMember(StructMember, String, String, String, String, int, int, String) Creates a hierarchical member StructMember - Parent (null for root), String - Name, String - Description, String - Icon ID, String - Tags, int - Data type, int - Scale, String - Format StructMember For creating hierarchical structures
getFreeLayer() Gets an available layer None int For organizing samples in layers
write(long, boolean, int, int, StructMember[]) Writes layered group data long - Position, boolean - Tag flag, int - Group order, int - Layer, StructMember[] - Members boolean For writing hierarchical grouped data

Working with Groups and Transactions

The impulse framework supports grouping samples to represent multi-step events or transactions. Groups are a powerful feature that enable you to model complex, related sequences of data as unified logical entities while preserving the individual samples and their timing relationships.

Common use cases for groups include:

  • Multi-stage transactions (request-processing-response)
  • Protocol sequences (handshake-data-teardown)
  • State machine transitions
  • Hierarchical events with sub-events
  • Parallel operations that need to be visually related

A group in impulse consists of samples that are linked by common group identifiers. Each sample in a group has an "order" attribute that specifies its position in the sequence (initial, intermediate, or final). Samples can also have a "layer" attribute that organizes them into parallel tracks within a group.

When viewed in the impulse UI, groups can be collapsed or expanded, and they enable powerful visualization options like Gantt charts and transaction tracing. This makes groups invaluable for understanding complex temporal relationships in your data.

Basic Group Handling

Groups are represented by an order value that specifies the position of a sample within its group:

// Constants for sample order in a group
int INITIAL = ISample.GO_INITIAL; // First sample in a group
int INTER = ISample.GO_INTER;     // Intermediate sample
int FINAL = ISample.GO_FINAL;     // Final sample in a group
int SINGLE = ISample.GO_SINGLE;   // Single-sample group

Method Descriptions:

Constant Value Description Notes
ISample.GO_INITIAL Integer constant Marks the first sample in a group Groups must start with this
ISample.GO_INTER Integer constant Marks intermediate samples in a group Can have multiple consecutive ones
ISample.GO_FINAL Integer constant Marks the last sample in a group Groups must end with this
ISample.GO_SINGLE Integer constant Marks a single-sample group For self-contained grouped samples

Writing Grouped Structured Data

For structured data, groups can be used to represent complex transactions:

// Create a structure with members (as in previous examples)
StructMember[] members = createMembers();

// Write the initial sample of a transaction
members[0].setValue("Begin Transaction");
members[1].setValue(0x1000);
writer.write(100, false, ISample.GO_INITIAL, 0, members);

// Write an intermediate sample
members[0].setValue("Processing");
members[1].setValue(0x1004);
writer.write(200, false, ISample.GO_INTER, 0, members);

// Write the final sample
members[0].setValue("Complete");
members[1].setValue(0x1008);
writer.write(300, false, ISample.GO_FINAL, 0, members);

Method Descriptions:

Method Description Parameters Return Value Notes
setValue(Object) Sets a member's value Object - Value for the member void Must match member's data type
write(long, boolean, int, int, StructMember[]) Writes grouped structured data long - Position, boolean - Tag flag, int - Group order, int - Layer, StructMember[] - Members void For multi-step transactions/processes

Using Layers

Layers provide additional organization for grouped samples:

// Write samples with different layers
int layer1 = 0;
int layer2 = 1;

// Layer 1 samples
writer.write(100, false, ISample.GO_INITIAL, layer1, members1);
writer.write(200, false, ISample.GO_FINAL, layer1, members2);

// Layer 2 samples
writer.write(150, false, ISample.GO_INITIAL, layer2, members3);
writer.write(250, false, ISample.GO_FINAL, layer2, members4);

Method Descriptions:

Method Description Parameters Return Value Notes
write(long, boolean, int, int, StructMember[]) Writes layered group data long - Position, boolean - Tag flag, int - Group order, int - Layer number, StructMember[] - Members void Layer numbers organize samples into parallel tracks

Advanced Writing Techniques

Beyond the basic operations of writing simple values, the impulse framework provides advanced capabilities that enable more sophisticated signal generation and manipulation. These techniques allow you to optimize performance, handle special cases, and create more complex signal patterns.

Advanced writing involves understanding the nuances of:

  • Continuous vs. discrete signal generation
  • Special position handling for sequential writes
  • Memory and performance optimization for large datasets
  • Error handling and validation during writing

Mastering these advanced techniques will help you create more efficient, robust signal-generation code, particularly when dealing with large datasets or performance-critical applications.

Working with Continuous vs. Discrete Signals

Continuous signals require a rate parameter when opening the writer:

// For continuous signals (samples at regular intervals)
writer.open(0, 10); // Start at 0, rate of 10 (e.g., 10 ns between samples)

// For discrete signals (samples at irregular intervals)
writer.open(0); // Start at 0, no rate needed

Method Descriptions:

Method Description Parameters Return Value Notes
open(long, long) Opens a continuous signal writer long - Starting position, long - Rate void Rate must be non-zero for continuous signals
open(long) Opens a discrete signal writer long - Starting position void No rate parameter means discrete signal

Working with Special Position Values

For continuous writers, you can use special position values:

// Use next sequential position (for continuous writers only)
writer.write(ISamplesWriter.NEXT_POSITION, false, value);

Method Descriptions:

Method/Constant Description Parameters/Value Return Value Notes
ISamplesWriter.NEXT_POSITION Special position constant long constant value N/A Used to automatically increment positions
write(long, boolean, T) Writes a value with special position long - Position (NEXT_POSITION), boolean - Tag flag, T - Value void Only works with continuous signals

Performance Considerations

Batch Writing

For better performance when writing many samples:

// Open writer
writer.open(0);

// Write samples in a loop without intermediate flushes
for (int i = 0; i < 1000; i++) {
    writer.write(i * 10, false, i * 100);
}

// Flush at the end
writer.flush();

// Close when done
writer.close(10000);

Method Descriptions:

Method Description Parameters Return Value Notes
open(long) Opens a writer long - Starting position void Must be called before writing
write(long, boolean, int) Writes integer value long - Position, boolean - Tag flag, int - Value void Batching multiple writes improves performance
flush() Flushes buffered writes None void Forces pending writes to be committed
close(long) Closes the writer long - End position void Finalizes the signal after writing

Best Practices

Creating high-quality, maintainable signal data requires following certain best practices in your writing code. These practices ensure that your signals will be correctly represented, efficiently generated, and easily analyzed in the impulse environment.

Good signal generation follows several key principles:

  • Using the appropriate writer interface for each signal type
  • Managing the writer lifecycle correctly
  • Handling position and time values consistently
  • Organizing related data using groups and layers
  • Optimizing performance for large datasets

By following these best practices, you can create signals that are not only technically correct but also optimized for analysis, visualization, and integration with other components of the impulse ecosystem.

Choosing the Right Writer Interface

  • Use ILogicSamplesWriter for digital signals (bits, buses)
  • Use IIntegerSamplesWriter for whole numbers (counters, IDs)
  • Use IFloatSamplesWriter for decimal values (measurements, analog signals)
  • Use IEventSamplesWriter for state transitions and events
  • Use ITextSamplesWriter for string data (messages, logs)
  • Use IBinarySamplesWriter for raw binary data (file contents, memory dumps)
  • Use IStructSamplesWriter for complex data with multiple fields (transactions, protocol messages)

Writer Lifecycle Management

  1. Always open the writer before writing samples
  2. Always close the writer when finished
  3. Ensure positions are non-decreasing (each position ≥ previous position)
  4. Use the appropriate tag levels to highlight important samples

Position Management

  1. Use domain-appropriate position values (nanoseconds, samples, frames, etc.)
  2. For continuous signals, always specify a rate when opening the writer
  3. For discrete signals, ensure positions are non-decreasing
  4. Close the writer with the final position to ensure proper signal range

Conclusion

The impulse framework's writer interfaces provide a powerful and flexible system for creating signal data. By understanding the hierarchy of interfaces and following the best practices, you can efficiently generate signals for analysis, visualization, and processing within the impulse environment.

Key takeaways from this guide include:

  1. Structured Writing Process: Following the writer lifecycle (open, write, close) ensures proper signal generation.
  2. Type-Specific Writers: Using the appropriate writer interface for each signal type provides type safety and optimal performance.
  3. Position Management: Understanding domain positions is crucial for creating signals with accurate timing relationships.
  4. Complex Data Modeling: Structured data types and grouped samples enable representation of sophisticated data models.
  5. Performance Optimization: Batch writing and proper memory management improve efficiency for large datasets.

Whether you're creating test data, converting from external formats, or programmatically generating signals for analysis, the writer interfaces provide the foundation for integrating your data into the impulse ecosystem. By leveraging these interfaces effectively, you can create rich, well-structured signals that unlock the full analytical power of the impulse platform.