
3 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
- Always open the writer before writing samples
- Always close the writer when finished
- Ensure positions are non-decreasing (each position ≥ previous position)
- Use the appropriate tag levels to highlight important samples
Position Management
- Use domain-appropriate position values (nanoseconds, samples, frames, etc.)
- For continuous signals, always specify a rate when opening the writer
- For discrete signals, ensure positions are non-decreasing
- 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:
- Structured Writing Process: Following the writer lifecycle (open, write, close) ensures proper signal generation.
- Type-Specific Writers: Using the appropriate writer interface for each signal type provides type safety and optimal performance.
- Position Management: Understanding domain positions is crucial for creating signals with accurate timing relationships.
- Complex Data Modeling: Structured data types and grouped samples enable representation of sophisticated data models.
- 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.
- Reading: Reading Samples and Iterating Samples
- Writing: Building Records