Moving forward. [seq] is the magic bullet. I've got it working on both Live / Workstation and Push 3S.
For my particular setup, I've hacked a small perl script to convert my ad hoc "TextMidi" format into the .txt format accepted by [seq]. Input looks like this:
Code: Select all
# Example of a .tmid (Text-based MIDI) file.
#
# TMidi format is Clint's ad hoc MIDI description language that allows comments, various input formats (hex) and
# control over the millisecond timing of the MIDI messages.
#
# These files are processed by the tmidi2txt.pl script into the bland .txt format read by Max4Live [seq] objects.
#
# Clint Goss <clint@goss.com> Copyright 2024 by Clint Goss, available under CC-BY 4.0
FIRST 10 # Initializes the timing counter (in Milliseconds) for the first emitted MIDI command (default 0).
STEP 30 # Sets the timing increment between MIDI commands (default 50).
192 10 x0A; # Single hex values
192 11 xFFFE0012; # Multiple hex values are allowed
+1 200 192 12; # + or - at the start indicates a *modification* to the timing counter (carries forward)
-5 xA0 # The ';' at the end is optional.
192 12;
# ending comment
... and output of the perl script for the above is ...
Code: Select all
10 192 10 10;
40 192 11 255 254 0 18;
71 200 192 12;
96 160;
126 192 12;
Here is the perl script:
Code: Select all
# tmidi2txt.pl
#
# tmidi2txt Name
#
# Converts a file in Tmidi format (Clint's MIDI description langauge, which allows comments and various input formats)
# to the bland .txt format read by the Max4Live [seq] object.
use strict; # Require declarations
use Getopt::Std; # Options processing
use vars qw($opt_c);
if (!getopts('c')) {
usage();
exit;
}
my $numArgs = $#ARGV + 1;
if ($numArgs < 1) {
usage();
exit;
}
sub usage () {
print <<EOM;
Usage: tmidi2txt.pl Name
Convert a file in Tmidi format (Clint's MIDI description langauge,
which allows comments and various input formats)
to the bland .txt format read by the Max4Live [seq] object.
EOM
}
my $Name = $ARGV[0];
my $inFileName = $Name . ".tmid";
my $outFileName = $Name . ".txt";
print "tmidi2txt: $inFileName => $outFileName\n";
open (INFILE, '<', $inFileName) || die "Cannot open $inFileName";
open (OUTFILE, '>', $outFileName) || die "Cannot open $outFileName";
my $inLine;
my $ms = 0; # Current millisecond counter
my $step = 50;
my $lineCount = 0;
while ($inLine = <INFILE>) {
my $outLine = "";
$lineCount++;
chomp ($inLine);
$inLine =~ s/^[ \t]+//;
# Directives that control the time counter
if ($inLine =~ /^FIRST/) {
# Initialize the millsecond counter
$inLine =~ s/^FIRST[ \t]+//;
$ms = int ($inLine);
next;
}
if ($inLine =~ /^STEP/) {
# Increment for the millsecond counter
$inLine =~ s/^STEP[ \t]+//;
$step = int ($inLine);
next;
}
if ($inLine =~ /^[+-]/) {
# Bias the running millisecond counter
my $bias = substr ($inLine, 0, 1);
$inLine =~ s/.//; # Eat the plus or minus
while ($inLine =~ /^[0-9]/) {
my $ch1 = substr ($inLine, 0, 1);
$inLine =~ s/.//;
$bias .= $ch1;
}
$ms += int ($bias);
$inLine =~ s/^[ \t]+//;
}
ThisLine: while (length ($inLine) > 0) {
$inLine =~ s/^[ \t]+//;
if ($inLine =~ /^#/) { last ThisLine; }
if ($inLine =~ /^x/) {
$inLine =~ s/.//; # Eat the "x"
# Pairs of Hex digits
while ($inLine =~ /^[0-9a-fA-F]/) {
my $ch1 = substr ($inLine, 0, 1);
my $ch2 = substr ($inLine, 1, 1);
$inLine =~ s/..//;
if ($ch2 !~ /[0-9a-fA-F]/) {
print STDERR "ERROR: Line $lineCount: bad hex number\n";
print STDERR " remaining line: [$inLine], ch1: [$ch1], ch2: [$ch2]\n";
last ThisLine;
}
$outLine .= hex ($ch1 . $ch2) . " ";
}
} elsif ($inLine =~ /^[0-9]/) {
# A base-10 number
while ($inLine =~ /^[0-9]/) {
my $ch1 = substr ($inLine, 0, 1, "");
$outLine .= $ch1;
}
} elsif ($inLine =~ /^;/) {
# Ignore
$inLine =~ s/.//; # Eat the ";"
} else {
print STDERR "ERROR: Line $lineCount: bad format / character unexpected\n";
print STDERR " remaining line: [$inLine]\n";
last ThisLine;
}
$outLine .= " ";
}
# Tighten up out masterpiece
while ($outLine =~ / /) {
$outLine =~ s/ / /g;
}
$outLine =~ s/ $//;
# Skip blank lines
if (length ($outLine) > 0) {
printf "%d %s;\n", $ms, $outLine;
printf OUTFILE "%d %s;\n", $ms, $outLine;
$ms += $step;
}
}
close INFILE || die "Cannot close $inFileName";
close OUTFILE || die "Cannot close $outFileName";
1;
My actual M4L device responds to a MIDI Note-On with pitch 0, or a button push (which I don't see how to actuate on Push3). Not sure how to attach a .AMXD device on this forum ... maybe I don't have enough "trust points" here? But it's pretty simple, just using [seq] at the core.
Here's my actual beginnings of a patch for one VL70 preset:
Code: Select all
# TMID (Text-based MIDI) for the VL70-m Thai Reed preset
#
# TMidi format is Clint's ad hoc MIDI description language that allows comments, various input formats (hex) and
# control over the millisecond timing of the MIDI messages.
#
# These files are processed by the tmidi2txt.pl script into the bland .txt format read by Max4Live [seq] objects.
#
# See WindSynthRig.xlsx [VL70 Roster] worksheet for the values of all VL70 patches.
#
# Clint Goss <clint@goss.com> Copyright 2024 by Clint Goss, available under CC-BY 4.0
# FIRST 10 # Initializes the timing counter (in Milliseconds) for the first emitted MIDI command (default 0).
# STEP 30 # Sets the timing increment between MIDI commands (default 50).
# LB = Yamaha VL70 List Book dated 29Jul2003
xB0 0 33 # Select the Preset1/Preset2/Custom/Internal Bank. CC 0. LB page 26, #4
# Done for each patch change "just in case" someone switched us out.
xB0 32 0 # Select the bank for this preset: 0=Pr1, 1=PR2, 2=Cst, 3=Int. CC 32. LB page 26, #4
xC0 106 # Program Change for our desired patch. LB page 26, #2
xB0 7 96 # Set the Volume - used to balance volumes across patches to minimized the need for volume
# adjustments when playing live. See WindSynthRig.xlsx [VL70 Roster]. CC 7.
# LB p. 26 #4, right column, Main Volume.