Compare commits

...

18 Commits

Author SHA1 Message Date
sshlien
b9c48dc778 2024.02.11 2024-02-11 13:59:25 -05:00
sshlien
eac28d9489 2024-02-09 2024-02-09 16:24:21 -05:00
sshlien
135e70c5e6 2024.02.07 2024-02-07 21:41:42 -05:00
sshlien
79e7ac2d97 2024.01.15 2024-01-15 17:22:56 -05:00
sshlien
ad41b9b053 2024.01.02 2024-01-03 09:41:14 -05:00
sshlien
b3d18d9722 2023.12.28 2023-12-28 14:57:49 -05:00
sshlien
9fa917b1bd 2023.12.23 2023-12-23 17:06:59 -05:00
sshlien
0dd1e063ae 2023.12.17 2023-12-17 08:46:38 -05:00
sshlien
69c1f850cb 2023.11.26 2023-11-26 12:51:23 -05:00
sshlien
f470c694ac 2023.11.17 2023-11-17 13:51:31 -05:00
sshlien
633e8d8848 2023.11.14 2023-11-14 16:36:10 -05:00
sshlien
d93cb473dc 2023.11.08 2023-11-08 10:57:05 -05:00
sshlien
b24803bf86 2023.11.01 2023-10-31 07:17:35 -04:00
sshlien
4e0266179b 2023.10.25 2023-10-25 19:43:27 -04:00
sshlien
2bf0052eb8 2023.08.13 2023-09-13 17:36:44 -04:00
sshlien
1394cd96c5 2023.09.11 2023-09-11 09:41:32 -04:00
sshlien
8a2ec3a898 2023.09.06 2023-09-06 17:37:58 -04:00
sshlien
48c443fabd 2023.08.31 2023-08-31 08:44:52 -04:00
9 changed files with 946 additions and 162 deletions

View File

@@ -1,2 +1,2 @@
August 22 2023
January 04 2024

View File

@@ -15103,4 +15103,127 @@ stats_noteon(). In function, output_track_summary(), suppressed
notemeanpitch for percussion channel.
October 25 2023
Midistats returns track activity (note on/off) for every track.
November 1 2023
Midistats returns the control volume settings for every track,
identifies midi files whose note timings are not quantized.
November 2 2023
abc2midi bug: In the following example, not all notes are
tied correctly.
X:1
T: tied note
M: 2/4
L: 1/4
K: C
D2-|:D2 |[1CD-:|[2CD|
No fix is available.
November 8 2023
midistats: extended the size of arrays (midievents and pulsecounter) to
handle certain midi files. The function stats_interpret_pulseCounter()
can detect midi files containing triplets and nonquantized notes.
midicopy: extended to handle midi files with up to 150 tracks.
December 17 2023
midicopy: The midi program number is not set properly for a few the
midi files where the channel program numbers are set in a separate
track. Midicopy processes and copies the tracks sequentially, and
by the time it sees the channel program numbers in the separate
track it is already too late. It would be necessary to rewrite
midicopy so that it stores the entire midi file in memory prior to
copying it to disk.
midistats: Introducing a new option -nseqfor n where is a channel
number. See drums.txt for a description.
December 23 2023
abc2midi: gchord bug
The following example produces some strange artefacts on starting
the midi file for some players.
X:1
T:Test
L:1/8
Q:1/2=30
M:2/2
%%MIDI gchord GHIc
K:C
GAB | "C"c4 C4 |
Explanation: the output midi file plays a note with midi pitch 0
in the accompaniment channel. The problem originates in the dogchords()
function for switch case 'G': in genmidi.c. Since g_started is 0,
the second branch of the if statement is executed, but the pitch
value in gchordnotes[gchordnotes_size] contains 0.
Fix: the switch statement should not be executed since g_started is
0 (i.e. the gchord accompaniment does not start till "C" is encountered).
The switch statement is now bypassed when either g_started
or gchords is 0. The tests for gchords and g_started in all the
case statements were removed since they are unnecessary.
December 28 2023
abc2midi: tuplet bug
The following example produces the error
Warning in line-char 7-8 : Different length notes in tuple
X:1
T:Test
L:1/4
Q:1/4=90
M:3/4
K:D
(3[ac']/d'/[ac']/ [ac']/z/ |
Analysis: though it is legal to have different length notes (and
rests) in a tuple, this is clearly a bug. The message occurs in the
function event_note() in store.c. tnote_num and tnote_denom should
contain the expected length of the note in the tuple based on the
first note encountered in the tuple. The value of tnote_denom was
not adjusted by event_chordoff to compensate by the length value
specified at the end of the [ac'] chord, resulting in the problem.
January 15 2024
abc2midi bug: the following example produces a warning, but the
the output midi file is correct.
Warning in line-char 7-23 : Track 1 Bar 1 has 1 time units while the time signature has 2
X:1
T:Test trill
L:1/4
M:2/2
Q:1/2=60
K:Dm
!trill!"C"g4- | g2^c2 |
Analysis: this warning is produced by checkbar() in genmidi.c. This problem was
reported in this file on November 23 2012. The code for handling tied notes is
quite intricate and I do not understand it. I am hesitant in tampering with the code.

View File

@@ -1,85 +0,0 @@
Advamced Percussion Analysis
in the Midistats Program
This is an addendum to the midistats.1 file.
The MIDI file devotes channel 9 to the percussion instruments
and over 60 percussion instruments are defined in the MIDI
standard. Though there is a lot of diversity in the percussion
track, for most MIDI files only the first 10 or so percussion
instruments are important in defining the character of the track. The
program Midiexplorer has various tools for exposing the percussion
channel which are described in the documentation. The goal
here is to find the essential characteristics of the percussion
track which distinguishes the MIDI files. This is attempted
in the program midistats. Here is a short description.
-corestats
Produces a line with 3 numbers separated by tabs. eg
384 8349 448
It returns the number of divisions per quarter note beat (ppqn),
the number of note onsets in the midi file, and the maximum
number of quarter note beats in midi file.
-pulseanalysis
Counts the number of note onsets as a function of its onset time
relative to a beat, grouping them into 12 intervals and returns
the result as a discrete probability density function. Generally,
the distribution consists of a couple of peaks corresponding
to quarter notes or eigth notes. If the distribution is flat,
it indicates that the times of the note occurrences have not been
quantized into beats and fractions. Here is a sample output.
0.3496,0.0000,0.0000,0.1602,0.0000,0.0002,0.2983,0.0000,0.0000,0.1914,0.0002,0.0001
-panal
Counts the number of note onsets for each percussion instrument. The first
number is the code (pitch) of the instrument, the second number is the
number of occurrences. eg.
35 337 37 16 38 432 39 208 40 231 42 1088 46 384 49 42 54 1104 57 5 70 1040 85 16
-ppatfor n
where n is the code number of the percussion instrument. Each beat
is represented by a 4 bit number where the position of the on-bit
indicates the time in the beat when the drum onset occurs. The bits
are ordered from left to right (higher order bits to lower order
bits). This is the order of bits that you would expect in a
time series.
Thus 0 indicates that there was no note onset in that beat, 1 indicates
a note onset at the end of the beat, 4 indicates a note onset
in the middle of the beat, and etc. The function returns a string
of numbers ranging from 0 to 7 indicating the presence of note onsets
for the selected percussion instrument for the sequence of beats
in the midi file. Here is a truncated sample of the output.
0 0 0 0 0 0 0 0 1 0 0 4 1 0 0 4 1 0 0 4 1 0 0 4 1 0 0 4 1 0 0 4 1 4 4 0
1 0 0 0 1 0 5 0 1 0 5 0 1 0 5 0 1 0 5 0 1 0 5 0 1 0 5 0 1 0 5 0 1 0 0 0
1 0 5 0 1 0 5 0 1 etc.
One can see a repeating 4 beat pattern.
-ppat
midistats attempts to find two percussion instruments in the midi file
which come closest to acting as the bass drum and snare drum.
If it is unsuccessful, it returns a message of its failue. Otherwise,
encodes the position of these drum onsets in a 8 bit byte for each
quarter note beat in the midi file. The lower (right) 4 bits encode the
bass drum and the higher (left) 4 bits encode the snare drum in the
same manner as described above for -ppatfor.
0 0 0 0 0 0 0 0 0 0 33 145 33 145 33 145 33 145 33 145 33 145 33 145 33 145
33 145 33 145 33 145 33 145 33 145 33 145 33 145 33 145 33 145 33 145 33 145
33 145 33 145 33 145 33 145 33 145 33 and etc.
-ppathist
computes and displays the histogram of the values that would appear
when running the -ppat. eg.
bass 35 337
snare 38 432
1 (0.1) 64 32 (2.0) 8 33 (2.1) 136 144 (9.0) 8 145 (9.1) 136
The bass percussion code, the number of onsets, and the snare
percussion code and the number of onsets are given in the
first two lines. In the next line the number of occurrences of
each value in the -ppat listing is given. The number in parentheses
splits the two 4-bit values with a period. Thus 33 = (2*16 + 1).

View File

@@ -1,4 +1,4 @@
.TH MIDISTATS 1 "9 December 2022"
.TH MIDISTATS 1 "11 February 2024"
.SH NAME
\fBmidistats\fP \- program to summarize the statistical properties of a midi file
.SH SYNOPSIS
@@ -26,7 +26,7 @@ applies.
program is followed by the channel number and the General Midi Program
number.
.PP
trkinfo is an array of 8 numbers which indicates the statistical properties
trkinfo is an array of 19 numbers which indicates the statistical properties
of the track of interest. The following data is given:
the channel number,
the first program assigned to this channel,
@@ -36,7 +36,18 @@ the sum of the MIDI pitches for all the notes,
the sum of the note durations in MIDI pulse units,
the number of control parameter messages,
the number of pressure messages.
and the number of distinct rhythm patterns for each channel
the number of distinct rhythm patterns for each channel
the number of pulses the channel was inactive
the minimum pitch value
the maximum pitch value
the minimum note length in pulses
the maximum note length in pulses
the number of gaps in the channel
the entropy of the pitch class histogram for that channel
the number of notes whose pitch were the same as the previous note
the number of notes whose pitch changed by less than 4 semitones
the number of notes whose pitch changed by 4 or more semitones
(In event of a chords the maximum pitches are compared.)
.PP
After processing all the individual tracks, the following information
applies to the entire midi file.
@@ -48,6 +59,8 @@ file.
.PP
pitchbends specifies the total number of pitchbends in this file.
.PP
pitchbendin c n specifies the number of pitchbends n in channel c
.PP
progs is a list of all the midi programs addressed
.PP
progsact the amount of activity for each of the above midi programs.
@@ -67,12 +80,30 @@ instruments.
pitches is a histogram for the 11 pitch classes (C, C#, D ...B)
that occur in the midi file.
.PP
key indicates the key of the music, the number of sharps (positive) or
flats (negative) in the key signature, and a measure of the confidence
in this key signature. The key was estimated from the above pitch histogram
by convolving with Craig Sapp's model. The peak of rmaj or rmin (below)
indicates the key. A correlation less than 0.4 indicates that the pitch
histogram does not follow the histogram of a major or minor scale.
(It may be the result of a mixture of two key signatures.)
.PP
rmaj the cross correlation coefficients with Craig Sapp's major key model
for each of the 11 keys (C, C#, D, ...,B).
.PP
rmaj the cross correlation coefficients with Craig Sapp's minor key model
for each of the 11 keys (C, C#, D, ...,B).
.PP
pitchact is a similar histogram but is weighted by the length of
the notes.
.PP
quietTime is used to compute the track/channel spread in midiexplorer.
It is computed by summing up all the midi pulses which occur
in gaps greater than 8 beats.
chanvol indicates the value of the control volume commands in the
midi file for each of the 16 channels. The maximum value is 127.
It scales the loudness of the notes (velocity) by its value.
.PP
chnact returns the amount of note activity in each channel.
.PP
trkact returns the number of notes in each track.
.PP
totalrhythmpatterns is the total number of bar rhythm patterns for
all channels except the percussion channel.
@@ -80,15 +111,209 @@ all channels except the percussion channel.
collisions. Midistats counts the bar rhythm patterns using a hashing
function. Presently collisions are ignored so occasionally two
distinct rhythm patterns are counted as one.
.SH Advance Percussion Analysis Tools
.PP
Midistats prints a number of arrays which may be useful in
determining where the music in the track is a melody line or
chordal rhythmic support. These arrays indicate the properties
for each of the 16 channels. (The percussion channel 9 contains
zeros.) In the case same channel occurs in several tracks, these
numbers are the totals for all track containing that channel.
Here is a description of these properties.
.PP
nnotes: the total number of notes in each channel
.br
nzeros: the number of notes whose previous note was the same pitch
.br
nsteps: the number of notes whose pitch difference with the previous
note was less than 4 semitones.
.br
njumps: the number of notes whose pitch difference with the previous
note was 4 or more semitones.
.br
rpats: the number of rhythmpatterns for each channels. This is a
duplication of data printed previously.
.br
pavg: the average pitch of all the notes for each channel.
.PP
In addition the midistats may return other codes that describe
other characteristics. They include
unquantized - the note onsets are not quantized
.br
triplets - 3 notes played in the time of 2 notes are present
.br
qnotes - the rhythm is basically simple
.br
clean_quantization - the note onsets are quantized into 1/4, 1/8, 1/16 time units.
.br
dithered_quantization - small variations in the quantized note onsets.
.br
Lyrics - lyrics are present in the meta data
.br
programcmd - there may be multiple program changes in a midi channel
.SH Advanced Percussion Analysis Tools
.PP
The MIDI file devotes channel 9 to the percussion instruments
and over 60 percussion instruments are defined in the MIDI
standard. Though there is a lot of diversity in the percussion
track, for most MIDI files only the first 10 or so percussion
instruments are important in defining the character of the track. The
program Midiexplorer has various tools for exposing the percussion
channel which are described in the documentation. The goal
here is to find the essential characteristics of the percussion
track which distinguishes the MIDI files. This is attempted
in the program midistats. Here is a short description.
.br
A number of experimental tools for analyzing the percussion channel
(track) were introduced into midistats and are accessible through
the runtime arguments. When these tools are used in a script which
runs through a collection of midi files, you can build a database
of percussion descriptors. Some more details are given in the
file drums.txt which comes with this documentation.
of percussion descriptors.
.SH OPTIONS
.PP
-corestats
.br
outputs a line with 5 numbers separated by tabs. eg
.br
1 8 384 4057 375
.br
It returns the number of tracks, the number of channels, the
number of divisions per quarter note beat (ppqn),
the number of note onsets in the midi file, and the maximum
number of quarter note beats in midi file.
.PP
-pulseanalysis
.br
counts the number of note onsets as a function of its onset time
relative to a beat, grouping them into 12 intervals and returns
the result as a discrete probability density function. Generally,
the distribution consists of a couple of peaks corresponding
to quarter notes or eigth notes. If the distribution is flat,
it indicates that the times of the note occurrences have not been
quantized into beats and fractions. Here is a sample output.
.br
0.349,0.000,0.000,0.160,0.000,0.000,0.298,0.000,0.000,0.191,0.000,0.000
.PP
-panal
.br
Counts the number of note onsets for each percussion instrument. The first
number is the code (pitch) of the instrument, the second number is the
number of occurrences. eg.
.br
35 337 37 16 38 432 39 208 40 231 42 1088 46 384 49 42 54 1104 57 5 70 1040 85 16
.PP
-ppatfor n
.br
where n is the code number of the percussion instrument. Each beat
is represented by a 4 bit number where the position of the on-bit
indicates the time in the beat when the drum onset occurs. The bits
are ordered from left to right (higher order bits to lower order
bits). This is the order of bits that you would expect in a
time series.
Thus 0 indicates that there was no note onset in that beat, 1 indicates
a note onset at the end of the beat, 4 indicates a note onset
in the middle of the beat, and etc. The function returns a string
of numbers ranging from 0 to 7 indicating the presence of note onsets
for the selected percussion instrument for the sequence of beats
in the midi file. Here is a truncated sample of the output.
.br
0 0 0 0 0 0 0 0 1 0 0 4 1 0 0 4 1 0 0 4 1 0 0 4 1 0 0 4 1 0 0 4 1 4 4 0
1 0 0 0 1 0 5 0 1 0 5 0 1 0 5 0 1 0 5 0 1 0 5 0 1 0 5 0 1 0 5 0 1 0 0 0
1 0 5 0 1 0 5 0 1 etc.
.br
One can see a repeating 4 beat pattern.
.PP
-ppat
.br
midistats attempts to find two percussion instruments in the midi file
which come closest to acting as the bass drum and snare drum.
If it is unsuccessful, it returns a message of its failue. Otherwise,
encodes the position of these drum onsets in a 8 bit byte for each
quarter note beat in the midi file. The lower (right) 4 bits encode the
bass drum and the higher (left) 4 bits encode the snare drum in the
same manner as described above for -ppatfor.
.br
0 0 0 0 0 0 0 0 0 0 33 145 33 145 33 145 33 145 33 145 33 145 33 145
.br
33 145 33 145 33 145 33 145 33 145 33 145 33 145 33 145 33 145 33 145
.br
33 145 33 145 33 145 33 145 33 145 33 and etc.
.PP
-ppathist
.br
computes and displays the histogram of the values that would appear
when running the -ppat. eg.
.br
bass 35 337
.br
snare 38 432
.br
1 (0.1) 64 32 (2.0) 8 33 (2.1) 136 144 (9.0) 8 145 (9.1) 136
.br
The bass percussion code, the number of onsets, and the snare
percussion code and the number of onsets are given in the
first two lines. In the next line the number of occurrences of
each value in the -ppat listing is given. The number in parentheses
splits the two 4-bit values with a period. Thus 33 = (2*16 + 1).
.PP
-pitchclass
.br
Returns the pitch class distribution for the entire midi file.
.PP
-nseqfor n
.br
Note sequence for channel n. This option produces a string of bytes
indicating the presence of a note in a time unit corresponding to
an eigth note. Thus each quarter note beat is represented by two
bytes. The pitch class is represented by the line number on the
staff, where 0 is C. Thus the notes on a scale are represented
by 7 numbers, and sharps and flats are ignored. The line number is
then converted to a bit position in the byte, so that the pitch
classes are represented by the numbers 1,2,4,8, and etc. A chord
of consisting of two note onsets would set two of the corresponding
bits. If we were to represent the full chromatic scale consisting
of 12 pitches, then we would require two-byte integers or
twice of much memory.
.br
Though the pitch resolution is not sufficient to distinguish
major or minor chords, it should be sufficient to be identify some
repeating patterns.
.PP
-nseq
.br
Same as above except it is applied to all channels except the
percussion channel.
.br
.PP
-nseqtokens
Returns the number of distinct sequence elements for each channel.
The channel number and number of distinct elements separated by
a comma is returned in a tab separated list for all active channels
except the percussion channel. Here is an example.
.br
2,3 3,4 4,11 5,6 6,3 7,3 8,6 9,3 11,2 12,1
.br
-ver (version number)
.SH AUTHOR

View File

@@ -1,12 +1,12 @@
abcMIDI : abc <-> MIDI conversion utilities
midi2abc version 3.59 February 08 2023
abc2midi version 4.84 January 06 2023
abc2midi version 4.85 December 23 2023
abc2abc version 2.20 February 07 2023
yaps version 1.92 January 06 2023
abcmatch version 1.82 June 14 2022
midicopy version 1.38 May 06 2022
midistats version 0.71 August 222023
midicopy version 1.39 November 08 2022
midistats version 0.87 February 11 2024
24th January 2002
Copyright James Allwright

View File

@@ -2413,30 +2413,26 @@ int j;
if ((chordnum == -1) && (action == 'c')) {
action = 'f';
};
if (gchords) /* [SS] 2021-06-27 */
if (gchords && g_started) /* [SS] 2021-06-27 2023-12-29*/
switch (action) {
case 'z':
break;
case 'f':
if (g_started && gchords) {
/* do fundamental */
if (inversion == -1)
save_note(g_num*len, g_denom, basepitch+fun.base, 8192, fun.chan, fun.vel);
else
save_note(g_num*len, g_denom, inversion+fun.base, 8192, fun.chan, fun.vel);
};
break;
case 'b':
if (g_started && gchords) {
/* do fundamental */
if (inversion == -1) /* [SS] 2014-11-02 */
save_note(g_num*len, g_denom, basepitch+fun.base, 8192, fun.chan, fun.vel);
else
save_note(g_num*len, g_denom, inversion+fun.base, 8192, fun.chan, fun.vel);
}
/* break; * [SS] 2021-06-27 2021-09-15 */
/* There should not be a break here so the switch statement continues into the next case 'c' */
@@ -2450,28 +2446,28 @@ int j;
break;
case 'g':
if(gchordnotes_size>0 && g_started && gchords)
if(gchordnotes_size>0)
save_note(g_num*len, g_denom, gchordnotes[0], 8192, gchord.chan, gchord.vel);
else /* [SS] 2016-01-03 */
save_note(g_num*len, g_denom, gchordnotes[gchordnotes_size], 8192, gchord.chan, gchord.vel);
break;
case 'h':
if(gchordnotes_size >1 && g_started && gchords)
if(gchordnotes_size >1)
save_note(g_num*len, g_denom, gchordnotes[1], 8192, gchord.chan, gchord.vel);
else /* [SS] 2016-01-03 */
save_note(g_num*len, g_denom, gchordnotes[gchordnotes_size], 8192, gchord.chan, gchord.vel);
break;
case 'i':
if(gchordnotes_size >2 && g_started && gchords)
if(gchordnotes_size >2)
save_note(g_num*len, g_denom, gchordnotes[2], 8192, gchord.chan, gchord.vel);
else /* [SS] 2016-01-03 */
save_note(g_num*len, g_denom, gchordnotes[gchordnotes_size], 8192, gchord.chan, gchord.vel);
break;
case 'j':
if(gchordnotes_size >3 && g_started && gchords)
if(gchordnotes_size >3)
save_note(g_num*len, g_denom, gchordnotes[3], 8192, gchord.chan, gchord.vel);
else /* [SS] 2016-01-03 */
save_note(g_num*len, g_denom, gchordnotes[gchordnotes_size], 8192, gchord.chan, gchord.vel);
@@ -2479,34 +2475,34 @@ int j;
/* [SS] 2021-12-10 */
case 'k':
if(gchordnotes_size >4 && g_started && gchords)
if(gchordnotes_size >4)
save_note(g_num*len, g_denom, gchordnotes[4], 8192, gchord.chan, gchord.vel);
else /* [SS] 2016-01-03 */
save_note(g_num*len, g_denom, gchordnotes[gchordnotes_size], 8192, gchord.chan, gchord.vel);
case 'G':
if(gchordnotes_size>0 && g_started && gchords)
if(gchordnotes_size>0 )
save_note(g_num*len, g_denom, gchordnotes[0]-12, 8192, gchord.chan, gchord.vel);
else /* [SS] 2016-01-03 */
save_note(g_num*len, g_denom, gchordnotes[gchordnotes_size], 8192, gchord.chan, gchord.vel);
break;
case 'H':
if(gchordnotes_size >1 && g_started && gchords)
if(gchordnotes_size >1)
save_note(g_num*len, g_denom, gchordnotes[1]-12, 8192, gchord.chan, gchord.vel);
else /* [SS] 2016-01-03 */
save_note(g_num*len, g_denom, gchordnotes[gchordnotes_size], 8192, gchord.chan, gchord.vel);
break;
case 'I':
if(gchordnotes_size >2 && g_started && gchords)
if(gchordnotes_size >2)
save_note(g_num*len, g_denom, gchordnotes[2]-12, 8192, gchord.chan, gchord.vel);
else /* [SS] 2016-01-03 */
save_note(g_num*len, g_denom, gchordnotes[gchordnotes_size], 8192, gchord.chan, gchord.vel);
break;
case 'J':
if(gchordnotes_size >3 && g_started && gchords)
if(gchordnotes_size >3)
save_note(g_num*len, g_denom, gchordnotes[3]-12, 8192, gchord.chan, gchord.vel);
else /* [SS] 2016-01-03 */
save_note(g_num*len, g_denom, gchordnotes[gchordnotes_size], 8192, gchord.chan, gchord.vel);
@@ -2514,7 +2510,7 @@ int j;
/* [SS] 2021-12-10 */
case 'K':
if(gchordnotes_size >3 && g_started && gchords)
if(gchordnotes_size >3)
save_note(g_num*len, g_denom, gchordnotes[4]-12, 8192, gchord.chan, gchord.vel);
else /* [SS] 2016-01-03 */
save_note(g_num*len, g_denom, gchordnotes[gchordnotes_size], 8192, gchord.chan, gchord.vel);

View File

@@ -52,7 +52,7 @@
#define VERSION "1.38 May 05 2022 midicopy"
#define VERSION "1.39 November 07 2023 midicopy"
#include "midicopy.h"
#define NULLFUNC 0
#define NULL 0
@@ -96,8 +96,9 @@ long max_currtime = 0;
long Mf_currcopytime = 0L; /* time of last copied event */
char *trackdata = NULL;
long trackdata_length, trackdata_size;
char *trackstr[64]; /* [SS] 2017-10-20 2019-07-05*/
int trackstr_length[64]; /* [SS] 2017-10-20 2019-07-05*/
/* char *trackstr[64]; [SS] 2017-10-20 2019-07-05*/
char *trackstr[150]; /* [SS] 2023-11-07 */
int trackstr_length[150]; /* [SS] 2017-10-20 2019-07-05* 2023-11-07*/
int trkid = 0;
int activetrack;
int nochanmsg = 1;
@@ -1290,7 +1291,7 @@ build_new_midi_file (format, ntracks, division, fp)
get_tempo_info_from_track_1 ();
if (ntracks > 63) {printf("too many tracks\n"); exit(1); }
if (ntracks > 149) {printf("too many tracks\n"); exit(1); }
/* The rest of the file is a series of tracks */
for (i = 0; i < ntracks; i++)
@@ -1836,7 +1837,7 @@ main (int argc, char *argv[])
printf ("-ver version information\n");
printf ("-trks n1,n2,..(starting from 1)\n");
printf ("-xtrks n1,n2,.. (tracks to exclude)\n"); /* [SS] 2013-10-27 */
printf ("-xchns n1,n2,.. (tracks to exclude)\n"); /* [SS] 2017-12-06 */
printf ("-xchns n1,n2,.. (channels to exclude)\n"); /* [SS] 2022-11-12 */
printf ("-chns n1,n2,..(starting from 1)\n");
printf ("-from n (in midi ticks)\n");
printf ("-to n (in midi ticks)\n");

View File

@@ -1,5 +1,4 @@
/* midistats - program to extract statistics from MIDI files
* Derived from midi2abc.c
/* Derived from midi2abc.c
* Copyright (C) 1998 James Allwright
* e-mail: J.R.Allwright@westminster.ac.uk
*
@@ -16,9 +15,25 @@
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
*/
#define VERSION "0.71 August 22 2023 midistats"
#define VERSION "0.87 February 11 2024 midistats"
/* midistrats.c is a descendent of midi2abc.c which was becoming to
large. The object of the program is to extract statistical characterisitic
of a midi file. It is mainly called by the midiexplorer.tcl application,
but it now used to create some databases using runstats.tcl which
comes with the midiexplorer package.
By default the program produces a summary that is described in the
midistats.1 man file. This is done by making a single pass through
the midi file. If the program is called with one of the runtime
options, the program extracts particular information by making more
than one pass. In the first pass it creates a table of all the
midievents which is stored in memory. The midievents are sorted in
time, and the requested information is extracted by going through
this table.
*/
#include <limits.h>
/* Microsoft Visual C++ Version 6.0 or higher */
@@ -49,10 +64,15 @@ extern char* strchr();
#include "midifile.h"
void initfuncs();
void stats_finish();
float histogram_entropy (int *histogram, int size);
float histogram_perplexity (int *histogram, int size);
void stats_noteoff(int chan,int pitch,int vol);
void stats_eot ();
void keymatch();
void outputChannelSummary();
void clearTrackNm ();
#define max(a,b) (( a > b ? a : b))
#define min(a,b) (( a < b ? a : b))
/* Global variables and structures */
@@ -62,9 +82,12 @@ static FILE *F;
static FILE *outhandle; /* for producing the abc file */
int tracknum=0; /* track number */
int lasttrack = 0; /* lasttrack */
int division; /* pulses per quarter note defined in MIDI header */
int halfdivision; /* pulses per eighth note */
int quietLimit; /* minimum number of pulses with no activity */
long tempo = 500000; /* the default tempo is 120 quarter notes/minute */
int bpm = 120; /*default tempo */
long laston = 0; /* length of MIDI track in pulses or ticks */
int key[12];
int sharps;
@@ -78,6 +101,7 @@ int percpattern;
int percpatternfor;
int percpatternhist;
int pitchclassanalysis;
int nseqfor;
int corestats;
int chordthreshold; /* number of maximum number of pulses separating note */
int beatsPerBar = 4; /* 4/4 time */
@@ -85,13 +109,14 @@ int divisionsPerBar;
int unitDivision;
int maximumPulse;
int lastBeat;
int hasLyrics = 0;
struct eventstruc {int onsetTime;
unsigned char channel;
unsigned char pitch;
unsigned char velocity;
;} midievents[40000];
;} midievents[50000];
int lastEvent = 0;
@@ -106,6 +131,7 @@ int percpattern = 0;
int percpatternfor = 0;
int percpatternhist = 0;
int pitchclassanalysis = 0;
int nseqfor = 0;
int corestats = 0;
@@ -114,12 +140,18 @@ int trackcount = 0;
int notechan[2048],notechanvol[2048]; /*for linking on and off midi
channel commands */
int last_tick[17]; /* for getting last pulse number in MIDI file */
int lastTick[2048]; /* for getting last pulse number for chan (0-15) and pitch (0-127) in MIDI file */
int last_on_tick[17]; /* for detecting chords [SS] 2019-08-02 */
int channel_active[17]; /* for dealing with chords [SS] 2023-08-30 */
int channel_used_in_track[17]; /* for dealing with quietTime [SS] 2023-09-06 */
int histogram[256];
unsigned char drumpat[8000];
unsigned char pseq[8000];
int pseqhist[128];
int percnum;
int nseqchn;
int nseqdistinct;
@@ -131,7 +163,11 @@ struct trkstat {
int notecount[17];
int chordcount[17];
int notemeanpitch[17];
int notepitchmin[17];
int notepitchmax[17];
int notelength[17];
int notelengthmin[17];
int notelengthmax[17];
int pitchbend[17];
int pressure[17];
int cntlparam[17];
@@ -141,6 +177,9 @@ struct trkstat {
int lastNoteOff[17];
int quietTime[17];
int rhythmpatterns[17];
int numberOfGaps[17];
int chanvol[17];
float pitchEntropy[17];
} trkdata;
/* The trkstat references the individual channels in the midi file.
@@ -156,14 +195,29 @@ struct trkstat {
* npulses is the number of pulses.
*/
struct notememory {int eighthUnit;
int nowPitch;
int beforePitch;
int previousPitch;
int zeroCount;
int stepCount;
int jumpCount;
int totalNotes;
int totalPitches;
} nm[17];
struct notememory tracknm;
int progcolor[17]; /* used by stats_program */
int drumhistogram[100]; /* counts drum noteons */
int pitchhistogram[12]; /* pitch distribution for non drum notes */
int channel2prog[17]; /* maps channel to program */
int channel2nnotes[17]; /*maps channel to note count */
int chnactivity[17]; /* [SS] 2018-02-02 */
int trkactivity[40]; /* [SS] 2023-10-25 */
int progactivity[128]; /* [SS] 2018-02-02 */
int pitchclass_activity[12]; /* [SS] 2018-02-02 */
int chanpitchhistogram[204]; /* [SS] 2023-09-13 */
/* [SS] 2017-11-01 */
@@ -186,7 +240,7 @@ static int progmapper[] = {
16, 16, 16, 16, 16, 16, 16, 16
};
int pulseCounter[480];
int pulseCounter[1024];
int pulseDistribution[24];
struct barPattern {
@@ -203,6 +257,7 @@ struct hashStruct {
int ncollisions = 0;
int nrpatterns = 0;
int nseqdistinct = 0;
void handle_collision () {
ncollisions++;
@@ -404,9 +459,11 @@ void stats_header (int format, int ntrks, int ldivision)
{
int i;
division = ldivision;
halfdivision = ldivision/2;
quietLimit = ldivision*8;
divisionsPerBar = division*beatsPerBar;
unitDivision = divisionsPerBar/24;
lasttrack = ntrks; /* [SS] 2023-10-25 */
printf("ntrks %d\n",ntrks);
printf("ppqn %d\n",ldivision);
chordthreshold = ldivision/16; /* [SS] 2018-01-21 */
@@ -419,6 +476,8 @@ void stats_header (int format, int ntrks, int ldivision)
trkdata.cntlparam[i] = 0; /* [SS] 2022-03-04 */
trkdata.pressure[i] = 0; /* [SS] 2022-03-04 */
trkdata.quietTime[i] = 0; /* [SS] 2022-08-22 */
trkdata.numberOfGaps[i] = 0; /* [SS] 2023-09-07 */
trkdata.chanvol[i] = 0; /* [SS] 2023-10-30 */
progcolor[i] = 0;
channel2prog[i] = 0; /* [SS] 2023-06-25-8/
channel2nnotes[i] = 0;
@@ -428,6 +487,7 @@ void stats_header (int format, int ntrks, int ldivision)
for (i=0;i<12;i++) pitchhistogram[i] = 0; /* [SS] 2017-11-01 */
for (i=0;i<12;i++) pitchclass_activity[i] = 0; /* [SS] 2018-02-02 */
for (i=0;i<128;i++) progactivity[i] = 0; /* [SS] 2018-02-02 */
for (i=0;i<40;i++) trkactivity[i]=0; /* [SS] 2023-10-25 */
}
void determine_progcolor ()
@@ -454,6 +514,55 @@ int i;
}
/* [SS] 2023-10-30 */
void stats_interpret_pulseCounter () {
int i,j;
int maxcount,ncounts,npeaks,npositives,peaklimit;
int maxloc;
float threshold,peak;
int decimate;
float tripletsCriterion8,tripletsCriterion4;
int resolution = 12;
int nzeros;
threshold = 10.0/(float) division;
maxcount = 0;
ncounts = 0;
npeaks = 0;
for (i=0;i<division;i++) {
ncounts = ncounts + pulseCounter[i];
if (pulseCounter[i] > maxcount) {
maxloc = i;
maxcount = pulseCounter[i];
}
}
peaklimit = (int) (ncounts * 0.020);
for (i=0;i<division;i++) {
if (pulseCounter[i] > peaklimit) npeaks++;
}
for (i = 0; i < resolution; i++) pulseDistribution[i] = 0;
decimate = division/resolution;
for (i = 0; i < division; i++) {
j = i/decimate;
pulseDistribution[j] += pulseCounter[i];
}
/* count zeros */
nzeros = 0;
for (i=0;i<resolution;i++) if((float) pulseDistribution[i]/(float) ncounts < 0.015 ) nzeros++;
npositives = resolution - nzeros;
if (nzeros > 3 && (float) pulseDistribution[resolution-1]/(float) ncounts < 0.1) {printf("clean_quantization\n");
} else if ((float) pulseDistribution[resolution-1]/(float) ncounts > 0.09 ||
npeaks > npositives) {printf("dithered_quantization\n");
} else {
peak = (float) maxcount/ (float) ncounts;
if (peak < threshold) printf("unquantized\n");
}
tripletsCriterion8 = (float) pulseDistribution[8]/ (float) ncounts;
tripletsCriterion4 = (float) pulseDistribution[4]/ (float) ncounts;
if (tripletsCriterion8 > 0.10 || tripletsCriterion4 > 0.10) printf("triplets\n");
if (pulseDistribution[0]/(float) ncounts > 0.95) printf("qnotes");
}
void stats_finish()
{
@@ -506,43 +615,48 @@ for (i=35;i<100;i++) {
printf("\npitches "); /* [SS] 2017-11-01 */
for (i=0;i<12;i++) printf("%d ",pitchhistogram[i]);
keymatch();
printf("\npitchact "); /* [SS] 2018-02-02 */
if (npulses > 0)
for (i=0;i<12;i++) printf("%5.2f ",pitchclass_activity[i]/(double) npulses);
else
for (i=0;i<12;i++) printf("%5.2f ",(double) pitchclass_activity[i]);
printf("\nchanvol "); /* [SS] 2023-10-30 */
for (i=1;i<17;i++) printf("%4d ",trkdata.chanvol[i]);
printf("\nchnact "); /* [SS] 2018-02-08 */
if (npulses > 0)
for (i=1;i<17;i++) printf("%5.2f ",chnactivity[i]/(double) trkdata.npulses[0]);
for (i=1;i<17;i++) printf("%5.3f ",chnactivity[i]/(double) trkdata.npulses[0]);
else
for (i=0;i<17;i++) printf("%5.2f ",(double) chnactivity[i]);
printf("\nquietTime ");
for (i=1;i<17;i++) {
delta = trkdata.npulses[0] - trkdata.quietTime[i];
if (trkdata.quietTime[i] < quietLimit) delta = 0;
delta = delta / (double) trkdata.npulses[0];
/* printf (" %5.3f ", delta); */
printf (" %d ", trkdata.quietTime[i]);
}
printf("\npitchentropy %f\n",histogram_entropy(pitchclass_activity,12));
for (i=0;i<17;i++) printf("%5.3f ",(double) chnactivity[i]);
printf("\ntrkact ");
lasttrack++;
for (i=0;i<lasttrack;i++) printf("% 5d",trkactivity[i]);
printf("\npitchperplexity %f\n",histogram_perplexity(pitchclass_activity,12));
printf("totalrhythmpatterns =%d\n",nrpatterns);
printf("collisions = %d\n",ncollisions);
if (hasLyrics) printf("Lyrics\n");
stats_interpret_pulseCounter ();
printf("\n");
outputChannelSummary();
}
float histogram_entropy (int *histogram, int size)
float histogram_perplexity (int *histogram, int size)
{
/* The perplexity is 2 to the power of the entropy */
int i;
int total;
float entropy;
float e,p;
total = 0;
entropy = 0.0;
//printf("\nhistogram_entropy of:");
for (i=0;i<size;i++) {
total += histogram[i];
//printf(" %d",histogram[i]);
}
for (i=0;i<size;i++) {
if (histogram[i] < 1) continue;
@@ -550,7 +664,8 @@ float histogram_entropy (int *histogram, int size)
e = p*log(p);
entropy = entropy + e;
}
return -entropy/log(2.0);
//printf("\n");
return pow(2.0,-entropy/log(2.0));
}
@@ -589,8 +704,16 @@ for (i=1;i<17;i++) {
else
printf("-1 0 ");
printf("%d %d ",trkdata.cntlparam[i],trkdata.pressure[i]); /* [SS] 2022-03-04 */
printf("%d %d",trkdata.quietTime[i],trkdata.rhythmpatterns[i]);
trkdata.quietTime[i] = 0;
printf("%d %d ",trkdata.quietTime[i],trkdata.rhythmpatterns[i]);
if (i != 10) {printf("%d %d %d %d %d",trkdata.notepitchmin[i], trkdata.notepitchmax[i] ,trkdata.notelengthmin[i], trkdata.notelengthmax[i], trkdata.numberOfGaps[i]);
printf(" %f",trkdata.pitchEntropy[i]);
} else
printf("-1 0");
trkdata.quietTime[i] = 0; /* in case channel i is used in another track */
trkdata.numberOfGaps[i] = 0;
if (lasttrack > 1) printf(" %d %d %d\n",tracknm.zeroCount,tracknm.stepCount,tracknm.jumpCount);
else
printf(" %d %d %d\n",nm[i-1].zeroCount,nm[i-1].stepCount,nm[i-1].jumpCount);
printf("\n");
channel2nnotes[i] += trkdata.notecount[i] + trkdata.chordcount[i];
@@ -604,26 +727,123 @@ void stats_trackstart()
{
int i;
tracknum++;
clearTrackNm ();
for (i=0;i<17;i++) {
trkdata.notecount[i] = 0;
trkdata.notemeanpitch[i] = 0;
trkdata.notepitchmin[i] = 128;
trkdata.notepitchmax[i] = 0;
trkdata.notelength[i] = 0;
trkdata.notelengthmin[i] = 10000;
trkdata.notelengthmax[i] = 0;
trkdata.chordcount[i] = 0;
trkdata.cntlparam[i] = 0;
last_tick[i] = -1;
last_on_tick[i] = -1;
channel_active[i] = 0;
}
printf("trk %d \n",tracknum);
for (i=0;i<2048;i++) lastTick[i] = -1;
for (i=0;i<17;i++) channel_used_in_track[i] = 0; /* [SS] 2023-09-06 */
for (i=0;i<204;i++) chanpitchhistogram[i] = 0; /* [SS] 2023-09-13 */
}
void stats_trackend()
{
trkdata.npulses[tracknum] = Mf_currtime;
int chan;
int i;
float entropy;
if (trkdata.npulses[0] < Mf_currtime) trkdata.npulses[0] = Mf_currtime;
for (chan = 1; chan < 17; chan++) /* [SS] 2023-09-06 */
if (channel_used_in_track[chan] > 0) trkdata.quietTime[chan] += (trkdata.npulses[0] - trkdata.lastNoteOff[chan]);
for (chan=0;chan<16;chan++) { /* 2023-09-13 */
if (chan == 9 || channel_used_in_track[chan+1] == 0) continue;
trkdata.pitchEntropy[chan+1] = histogram_perplexity(chanpitchhistogram +chan*12,11);
}
output_track_summary();
}
void clearNotememory () {
int i;
for (i=0;i<17;i++) {
nm[i].eighthUnit = 0;
nm[i].nowPitch = 0;
nm[i].beforePitch = 0;
nm[i].previousPitch = 0;
nm[i].zeroCount = 0;
nm[i].stepCount = 0;
nm[i].jumpCount = 0;
nm[i].totalNotes =0;
nm[i].totalPitches =0;
}
}
void clearTrackNm () {
tracknm.eighthUnit = 0;
tracknm.nowPitch = 0;
tracknm.beforePitch = 0;
tracknm.previousPitch = 0;
tracknm.zeroCount = 0;
tracknm.stepCount = 0;
tracknm.jumpCount = 0;
tracknm.totalNotes = 0;
tracknm.totalPitches = 0;
}
void updateNotememory (int unit, int chn, int pitch) {
int deltaPitch;
if (chn == 9) return;
if (unit == nm[chn].eighthUnit) {
if (pitch > nm[chn].nowPitch) nm[chn].nowPitch = pitch;
return;
}
/* unit is different */
nm[chn].beforePitch = nm[chn].nowPitch;
nm[chn].nowPitch = pitch;
if (nm[chn].previousPitch > 0)
{
deltaPitch = nm[chn].beforePitch - nm[chn].previousPitch;
if (deltaPitch < 0) deltaPitch = -deltaPitch;
if (deltaPitch == 0) nm[chn].zeroCount++;
else if (deltaPitch < 4) nm[chn].stepCount++;
else nm[chn].jumpCount++;
}
if (nm[chn].beforePitch != 0) nm[chn].previousPitch = nm[chn].beforePitch;
nm[chn].eighthUnit = unit;
nm[chn].totalNotes++;
nm[chn].totalPitches = nm[chn].totalPitches + pitch;
}
void updateTrackNotememory (int unit, int chn, int pitch) {
int deltaPitch;
if (chn == 9) return;
if (unit == tracknm.eighthUnit) {
if (pitch > tracknm.nowPitch) tracknm.nowPitch = pitch;
return;
}
/* unit is different */
tracknm.beforePitch = tracknm.nowPitch;
tracknm.nowPitch = pitch;
if (tracknm.previousPitch > 0)
{
deltaPitch = tracknm.beforePitch - tracknm.previousPitch;
if (deltaPitch < 0) deltaPitch = -deltaPitch;
if (deltaPitch == 0) tracknm.zeroCount++;
else if (deltaPitch < 4) tracknm.stepCount++;
else tracknm.jumpCount++;
}
if (tracknm.beforePitch != 0) tracknm.previousPitch = tracknm.beforePitch;
tracknm.eighthUnit = unit;
/*printf("%d, %d, %d, %d, %d, %d %d\n",unit,nm[chn].beforePitch,nm[chn].previousPitch,\
deltaPitch,nm[chn].zeroCount,nm[chn].stepCount,nm[chn].jumpCount);
*/
}
void stats_noteon(chan,pitch,vol)
int chan, pitch, vol;
@@ -631,8 +851,13 @@ int chan, pitch, vol;
int delta;
int barnum;
int unit;
int eigthunit;
int dithermargin; /* [SS] 2023-08-22 */
int cpitch; /* [SS] 2023-09-13 */
int pulsePosition;
cpitch = pitch % 12;
channel_used_in_track[chan+1]++; /* [SS] 2023-09-06 */
dithermargin = unitDivision/2 - 1;
if (vol == 0) {
/* treat as noteoff */
@@ -640,18 +865,26 @@ int chan, pitch, vol;
trkdata.lastNoteOff[chan+1] = Mf_currtime; /* [SS] 2022.08.22 */
return;
}
pulsePosition = Mf_currtime % division;
pulseCounter[pulsePosition]++;
if (pulsePosition >= 1023) {printf("pulsePosition = %d too large\n",pulsePosition);
exit(1);
}
trkdata.notemeanpitch[chan+1] += pitch;
trkdata.notepitchmax[chan+1] = max(trkdata.notepitchmax[chan+1],pitch);
trkdata.notepitchmin[chan+1] = min(trkdata.notepitchmin[chan+1],pitch);
if (trkdata.lastNoteOff[chan+1] >= 0) {
delta = Mf_currtime - trkdata.lastNoteOff[chan+1];
trkdata.lastNoteOff[chan+1] = -1; /* in case of chord */
if (delta > quietLimit) {
trkdata.quietTime[chan+1] += delta;
trkdata.numberOfGaps[chan+1]++;
trkdata.lastNoteOff[chan+1] = -1; /* in case of chord */
}
}
if (abs(Mf_currtime - last_on_tick[chan+1]) < chordthreshold) trkdata.chordcount[chan+1]++;
else trkdata.notecount[chan+1]++; /* [SS] 2019-08-02 */
last_tick[chan+1] = Mf_currtime;
lastTick[chan*128 + pitch] = Mf_currtime;
last_on_tick[chan+1] = Mf_currtime; /* [SS] 2019-08-02 */
/* last_on_tick not updated by stats_noteoff */
@@ -669,6 +902,11 @@ int chan, pitch, vol;
unit = ((Mf_currtime+dithermargin) % divisionsPerBar)/unitDivision;
//printf("unit = %d pattern = %d \n",unit,barChn[chan].rhythmPattern);
barChn[chan].rhythmPattern = barChn[chan].rhythmPattern |= (1UL << unit);
chanpitchhistogram[chan*12+cpitch]++; /* [SS] 2023-09-13 */
eigthunit = Mf_currtime/halfdivision;
updateNotememory (eigthunit, chan, pitch);
updateTrackNotememory (eigthunit, chan, pitch);
}
@@ -679,6 +917,8 @@ int chan, pitch, vol;
else drumhistogram[pitch]++;
}
else pitchhistogram[pitch % 12]++; /* [SS] 2017-11-01 */
channel_active[chan+1]++;
}
@@ -691,17 +931,28 @@ void stats_noteoff(int chan,int pitch,int vol)
int length;
int program;
/* ignore if there was no noteon */
if (last_tick[chan+1] == -1) return;
length = Mf_currtime - last_tick[chan+1];
if (lastTick[chan*128+pitch] == -1) return;
length = Mf_currtime - lastTick[chan*128+pitch];
trkdata.notelength[chan+1] += length;
trkdata.notelengthmax[chan+1] = max(trkdata.notelengthmax[chan+1],length);
trkdata.notelengthmin[chan+1] = min(trkdata.notelengthmin[chan+1],length);
//if (length < 3) printf("chan = %d lasttick = %d currtime = %ld\n",chan,lastTick[chan*128+pitch],Mf_currtime);
trkdata.lastNoteOff[chan+1] = Mf_currtime; /* [SS] 2022.08.22 */
chnactivity[chan+1] += length;
trkactivity[tracknum]++;
if (chan == 9) return; /* drum channel */
pitchclass_activity[pitch % 12] += length;
program = trkdata.program[chan+1];
progactivity[program] += length;
channel_active[chan+1]--;
/* [SS] 2018-04-18 */
if(Mf_currtime > last_tick[chan+1]) last_tick[chan+1] = Mf_currtime;
if(Mf_currtime > lastTick[chan*128+pitch] && channel_active[chan+1] == 0)
lastTick[chan*128+pitch] = Mf_currtime; /* [SS] 2023.08.30 handle chords */
if (length > 4800) {
lastTick[chan*128+pitch] = Mf_currtime; /* handle stuck note [SS] 2023.08.30 */
channel_active[chan+1] = 0;
}
}
@@ -743,11 +994,16 @@ if (trkdata.program[chan+1] != 0) {
void stats_parameter(chan,control,value)
int chan, control, value;
{
/*if (control == 7) {
printf("cntrlvolume %d %d \n",chan+1,value);
}
int chan1;
chan1 = chan+1;
/* There may be many volume commands for the same channel. Only
record the first one.
*/
trkdata.cntlparam[chan+1]++;
if (control == 7 && trkdata.chanvol[chan1] == 0) {
/*printf("cntrlvolume %d %d \n",chan+1,value);*/
trkdata.chanvol[chan1] = value; /* [SS] 2023-10-30 */
}
trkdata.cntlparam[chan1]++;
}
@@ -757,6 +1013,7 @@ int type, leng;
char *mess;
{
int i;
if (type == 5) hasLyrics = 1; /* [SS] 2023-10-30 */
if (type != 3) return;
printf("metatext %d ",type);
for (i=0;i<leng;i++) printf("%c",mess[i]);
@@ -819,9 +1076,11 @@ midievents[lastEvent].channel = chan;
midievents[lastEvent].pitch = pitch;
midievents[lastEvent].velocity = vol;
lastEvent++;
if (lastEvent > 39999) {printf("ran out of space in midievents structure\n");
if (lastEvent > 49999) {printf("ran out of space in midievents structure\n");
exit(1);
}
/*if (lastEvent < 20) {printf("record_noteon %d %d %d %ld\n",chan,pitch,vol,Mf_currtime/halfdivision);}*/
channel_active[chan+1]++;
}
void record_noteoff(int chan,int pitch,int vol)
@@ -830,6 +1089,12 @@ void record_noteoff(int chan,int pitch,int vol)
void record_trackend()
{
}
void record_tempo(long ltempo)
{
tempo = ltempo;
if (bpm == 120) bpm = 60000000.0/tempo;
tempocount++;
}
int int_compare_events(const void *a, const void *b) {
struct eventstruc *ia = (struct eventstruc *)a;
@@ -843,12 +1108,17 @@ int int_compare_events(const void *a, const void *b) {
void load_header (int format, int ntrks, int ldivision)
{
int i;
division = ldivision;
halfdivision = ldivision/2;
lasttrack = ntrks;
for (i=0;i<17;i++) channel_active[i] = 0; /* for counting number of channels*/
}
void initfunc_for_stats()
{
int i;
Mf_error = stats_error; /* [SS] 2017-11-19 */
Mf_header = stats_header;
Mf_trackstart = stats_trackstart;
@@ -871,6 +1141,7 @@ void initfunc_for_stats()
Mf_seqspecific = no_op3;
Mf_text = stats_metatext;
Mf_arbitrary = no_op2;
for (i = 0; i< 1023; i++) pulseCounter[i] = 0;
}
@@ -893,7 +1164,7 @@ void initfunc_for_loadNoteEvents()
Mf_eot = no_op0;
Mf_timesig = no_op4;
Mf_smpte = no_op5;
Mf_tempo = no_op1;
Mf_tempo = record_tempo;
Mf_keysig = no_op2;
Mf_seqspecific = no_op3;
Mf_text = no_op3;
@@ -922,16 +1193,16 @@ int pulsePosition;
int decimate;
float fraction;
int resolution = 12;
for (i = 0; i< 480; i++) pulseCounter[i] = 0;
for (i = 0; i< 1023; i++) pulseCounter[i] = 0;
for (i = 0; i < lastEvent; i++) {
pulsePosition = midievents[i].onsetTime % division;
pulseCounter[pulsePosition]++;
if (pulsePosition >= 480) {printf("pulsePosition = %d too large\n",pulsePosition);
if (pulsePosition >= 1023) {printf("pulsePosition = %d too large\n",pulsePosition);
exit(1);
}
}
for (i = 0; i < resolution; i++) pulseDistribution[i] = 0;
/*for (i = 0; i < 480; i++) printf(" %d",pulseCounter[i]);
/*for (i = 0; i < 1023; i++) printf(" %d",pulseCounter[i]);
printf("\n");
*/
decimate = division/resolution;
@@ -994,6 +1265,104 @@ for (i = 0; i <lastEvent; i++) {
}
}
static int pitch2noteseq[] = {
0, 0, 1, 1, 2, 3, 3, 4,
4, 5, 5, 6};
void noteseqmap(int chn) {
int i;
int half;
int channel;
int pitchclass;
int onset;
int index;
int remainder;
int noteNum;
int part;
half = division/2;
for (i = 0; i<8000; i++) pseq[i] = 0;
for (i = 0; i <lastEvent; i++) {
channel = midievents[i].channel;
if (channel == 9) continue; /* ignore percussion channel */
if (channel == chn || chn == -1) {
pitchclass = midievents[i].pitch % 12;
noteNum = pitch2noteseq[pitchclass];
onset = midievents[i].onsetTime;
index = onset/half;
if (index >= 8000) {printf("index too large in drumpattern\n");
break;
}
pseq[index] = pseq[index] |= 1 << noteNum;
}
/*printf("pitchclass = %d noteNum =%d index = %d pseq[index] %d \n",pitchclass, noteNum, index, pseq[index]); */
}
}
void print_pseq () {
int i;
for (i=0;i<(lastBeat+1)*2;i++) {
printf("%d ",pseq[i]);
if (i >= 8000) break;
}
printf("\n");
}
int noteseqhist(int chan) {
int nonzeros;
int i;
nonzeros = 0;
noteseqmap(chan);
for (i=0;i<128;i++) {
pseqhist[i] = 0;
}
for (i=0;i<lastBeat;i++) {
pseqhist[pseq[i]]++;
}
for (i=0;i<128;i++) {
if (pseqhist[i] > 0)
nonzeros++;;
}
return nonzeros;
}
void allDistinctNoteSeq() {
int i;
int nonzeros;
for (i=0;i<17;i++) {
/*printf("\n%d,%d",i,channel_active[i+1]);*/
if (i == 9) continue;
if (channel_active[i+1] == 0) continue;
nonzeros = noteseqhist(i);
if (channel_active[i+1] > 0) printf("\t%d,%d",i+1,nonzeros);
}
printf("\n");
}
void outputChannelSummary() {
int i;
for (i=0;i<17;i++) {
printf("nnotes: ");
for(i=0;i<16;i++) printf(" %d",nm[i].totalNotes);
printf("\nnzeros: ");
for(i=0;i<16;i++) printf(" %d",nm[i].zeroCount);
printf("\nnsteps: ");
for(i=0;i<16;i++) printf(" %d",nm[i].stepCount);
printf("\nnjumps: ");
for(i=0;i<16;i++) printf(" %d",nm[i].jumpCount);
printf("\nrpats: ");
for(i=1;i<17;i++) printf(" %d",trkdata.rhythmpatterns[i]);
printf("\npavg: ");
/* avoid dividing by 0 */
for(i=0;i<16;i++) printf(" %d",nm[i].totalPitches/(1+nm[i].totalNotes));
printf("\n");
}
}
void dualDrumPattern (int perc1, int perc2) {
int i;
int channel;
@@ -1070,6 +1439,120 @@ for (i=0;i<lastBeat;i++) printf("%d ",drumpat[i]);
printf("\n");
}
/*
The key match algorithm is based on the work of Craig Sapp
Visual Hierarchical Key Analysis
https://ccrma.stanford.edu/~craig/papers/05/p3d-sapp.pdf
published in Proceedings of the International Computer Music
Conference,2001,
and the work of Krumhansl and Schmukler.
Craig Sapp's simple coefficients (mkeyscape)
Major C scale
The algorithm correlates the pitch class class histogram with
the ssMj or ssMn coefficients trying all 12 key centers, and
looks for a maximum.
The algorithm returns the key, sf (the number of sharps or
flats), and the maximum peak which is relatable to the
level of confidence we have of the result.
*/
static float ssMj[] = { 1.25, -0.75, 0.25, -0.75, 0.25, 0.25,
-0.75, 1.25, -0.75, 0.25, -0.75, 0.25};
/* Minor C scale (3 flats)
*/
static float ssMn[] = { 1.25, -0.75, 0.25, 0.25, -0.75, 0.25,
-0.75, 1.25, 0.25, -0.75, 0.25, -0.75};
static char *keylist[] = {"C", "C#", "D", "Eb", "E", "F",
"F#", "G", "Ab", "A", "Bb", "B"};
static char *majmin[] = {"maj", "min"};
/* number of sharps or flats for major keys in keylist */
static int maj2sf[] = {0, 7, 2, -3, 4, -1, 6, 1, -4, 3, -2, 5};
static int min2sf[] = {-3, 4, -1, -6, -4, 3, -4 -2, -7, 0, -5, 2};
void keymatch () {
int i;
int r;
int k;
float c2M,c2m,h2,hM,hm;
float rmaj[12],rmin[12];
float hist[12];
float best;
int bestIndex,bestMode;
int sf; /* number of flats or sharps (flats negative) */
int total;
float fnorm;
c2M = 0.0;
c2m = 0.0;
h2 = 0.0;
best = 0.0;
bestIndex = 0;
bestMode = -1;
total =0;
for (i=0;i<12;i++) {
total += pitchhistogram[i];
}
for (i=0;i<12;i++) {
hist[i] = (float) pitchhistogram[i]/(float) total;
}
fnorm = 0.0;
for (i=0;i<12;i++) {
fnorm = hist[i]*hist[i] + fnorm;
}
fnorm = sqrt(fnorm);
for (i=0;i<12;i++) {
hist[i] = hist[i]/fnorm;
}
for (i=0;i<12;i++) {
c2M += ssMj[i]*ssMj[i];
c2m += ssMn[i]*ssMn[i];
h2 += hist[i]*hist[i];
}
if (h2 < 0.0001) {
printf("zero histogram\n");
return;
}
for (r=0;r<12;r++) {
hM = 0.0;
hm = 0.0;
for (i=0;i<12;i++) {
k = (i - r) % 12;
if (k < 0) k = k + 12;
hM += hist[i]*ssMj[k];
hm += hist[i]*ssMn[k];
}
rmaj[r] = hM/sqrt(h2*c2M);
rmin[r] = hm/sqrt(h2*c2m);
}
for (r=0;r<12;r++) {
if(rmaj[r] > best) {
best = rmaj[r];
bestIndex = r;
bestMode = 0;
}
if(rmin[r] > best) {
best = rmin[r];
bestIndex = r;
bestMode = 1;
}
}
if (bestMode == 0) sf = maj2sf[bestIndex];
else sf = min2sf[bestIndex];
printf("\nkey %s%s %d %f",keylist[bestIndex],majmin[bestMode],sf,best);
printf("\nrmaj ");
for (r=0;r<12;r++) printf("%7.3f",rmaj[r]);
printf("\nrmin ");
for (r=0;r<12;r++) printf("%7.3f",rmin[r]);
}
void percsummary () {
@@ -1113,7 +1596,13 @@ printf("\n");
void corestatsOutput() {
printf("%d\t%d\t%d\n", division,lastEvent,lastBeat);
int i;
int nchannels;
nchannels = 0;
for (i=1;i<17;i++)
if (channel_active[i] > 0) nchannels++;
printf("%d\t%d\t%d\t%d\t%d\t%d\n",lasttrack,nchannels, division,bpm,lastEvent,lastBeat);
/*printf("%d\n",tempocount);*/
}
@@ -1256,6 +1745,31 @@ int argc;
}
}
arg = getarg("-nseqfor",argc,argv);
if (arg != -1) {
nseqfor = 1;
stats = 0;
if (arg != -1 && arg <argc) {
nseqchn = readnum(argv[arg]);
printf("nseqch = %d\n",nseqchn);
}
}
arg = getarg("-nseq",argc,argv);
if (arg != -1) {
nseqfor = 1;
stats = 0;
nseqchn = -1;
}
arg = getarg("-nseqtokens",argc,argv);
if (arg != -1) {
nseqdistinct = 1;
stats = 0;
}
arg = getarg("-ppathist",argc,argv);
if (arg != -1) {
percpatternhist = 1;
@@ -1293,9 +1807,12 @@ int argc;
printf(" -pulseanalysis\n");
printf(" -panal\n");
printf(" -ppat\n");
printf(" -ppatfor\n");
printf(" -ppatfor pitch\n");
printf(" -ppathist\n");
printf(" -pitchclass\n");
printf(" -nseq\n");
printf(" -nseqfor channel\n");
printf(" -nseqtokens\n");
printf(" -ver version number\n");
printf(" -d <number> debug parameter\n");
printf(" The input filename is assumed to be any string not\n");
@@ -1347,6 +1864,13 @@ if (percpatternhist) {
percsummary();
drumPatternHistogram();
}
if (nseqfor) {
noteseqmap(nseqchn);
print_pseq();
}
if (nseqdistinct) {
allDistinctNoteSeq();
}
if (corestats) corestatsOutput();
if (pitchclassanalysis) {
pitchClassAnalysis();
@@ -1367,6 +1891,6 @@ int argc;
if(stats == 1) midistats(argc,argv);
if(pulseanalysis || corestats || percanalysis ||\
percpatternfor || percpattern || percpatternhist ||\
pitchclassanalysis) loadEvents();
pitchclassanalysis || nseqfor || nseqdistinct) loadEvents();
return 0;
}

View File

@@ -186,7 +186,7 @@ int main()
*/
#define VERSION "4.84 January 20 2023 abc2midi"
#define VERSION "4.85 December 23 2023 abc2midi"
/* enables reading V: indication in header */
#define XTEN1 1