Browse Source

allow dropping midi files onto existing tracks (~alextee/zrythm-feature#240), fix MIDI import not adding note offs when velocity is 0, fix crash when importing MIDI file that uses note ons with velocity 0 as note offs

serd_style_docs
Alexandros Theodotou 2 years ago
parent
commit
9b4a5c014a
Signed by: alex
GPG Key ID: 022EAE42313D70F3
  1. 5
      inc/audio/midi_file.h
  2. 4
      inc/audio/region.h
  3. 19
      src/actions/tracklist_selections.c
  4. 4
      src/audio/midi_file.c
  5. 210
      src/audio/midi_region.c
  6. 93
      src/audio/tracklist.c
  7. 37
      tests/actions/tracklist_selections.c
  8. BIN
      tests/format_1_two_tracks_with_data.mid
  9. 2
      tests/meson.build

5
inc/audio/midi_file.h

@ -26,6 +26,8 @@ @@ -26,6 +26,8 @@
#ifndef __AUDIO_MIDI_FILE_H__
#define __AUDIO_MIDI_FILE_H__
#include <stdbool.h>
/**
* @addtogroup audio
*
@ -37,7 +39,8 @@ @@ -37,7 +39,8 @@
*/
int
midi_file_get_num_tracks (
const char * abs_path);
const char * abs_path,
bool non_empty_only);
/**
* @}

4
inc/audio/region.h

@ -129,6 +129,10 @@ typedef struct ZRegion @@ -129,6 +129,10 @@ typedef struct ZRegion
* from MIDI files.
*
* FIXME allocate.
*
* @note These are present in
* \ref ZRegion.midi_notes and must not be
* free'd separately.
*/
MidiNote * unended_notes[12000];
int num_unended_notes;

19
src/actions/tracklist_selections.c

@ -121,7 +121,7 @@ tracklist_selections_action_new ( @@ -121,7 +121,7 @@ tracklist_selections_action_new (
{
self->num_tracks =
midi_file_get_num_tracks (
self->file_descr->abs_path);
self->file_descr->abs_path, true);
}
else
{
@ -389,17 +389,17 @@ create_track ( @@ -389,17 +389,17 @@ create_track (
pl->id.track_pos == track->pos);
}
Position start_pos;
position_init (&start_pos);
if (self->have_pos)
{
position_set_to_pos (
&start_pos, &self->pos);
}
if (self->track_type == TRACK_TYPE_AUDIO)
{
/* create an audio region & add to
* track */
Position start_pos;
position_init (&start_pos);
if (self->have_pos)
{
position_set_to_pos (
&start_pos, &self->pos);
}
ZRegion * ar =
audio_region_new (
self->pool_id,
@ -419,9 +419,6 @@ create_track ( @@ -419,9 +419,6 @@ create_track (
{
/* create a MIDI region from the MIDI
* file & add to track */
Position start_pos;
position_set_to_pos (
&start_pos, PLAYHEAD);
ZRegion * mr =
midi_region_new_from_midi_file (
&start_pos,

4
src/audio/midi_file.c

@ -28,13 +28,15 @@ @@ -28,13 +28,15 @@
*/
int
midi_file_get_num_tracks (
const char * abs_path)
const char * abs_path,
bool non_empty_only)
{
MIDI_FILE * mf =
midiFileOpen (abs_path);
g_return_val_if_fail (mf, -1);
int num = midiReadGetNumTracks (mf);
g_debug ("%s: num tracks = %d", abs_path, num);
midiFileClose (mf);
return num;

210
src/audio/midi_region.c

@ -403,6 +403,8 @@ midi_region_new_from_midi_file ( @@ -403,6 +403,8 @@ midi_region_new_from_midi_file (
"%s: reading from %s...", __func__, abs_path);
ZRegion * self = object_new (ZRegion);
ArrangerObject * r_obj =
(ArrangerObject *) self;
self->id.type = REGION_TYPE_MIDI;
@ -410,14 +412,12 @@ midi_region_new_from_midi_file ( @@ -410,14 +412,12 @@ midi_region_new_from_midi_file (
midiFileOpen (abs_path);
g_return_val_if_fail (mf, NULL);
bool print_details = false;
char str[128];
int ev;
MIDI_MSG msg;
int i, iNum;
unsigned int j;
Position pos;
Position pos, global_pos;
MidiNote * mn;
double ticks;
@ -448,12 +448,11 @@ midi_region_new_from_midi_file ( @@ -448,12 +448,11 @@ midi_region_new_from_midi_file (
((double) msg.dwAbsPos * transport_ppqn) /
ppqn;
position_from_ticks (&pos, ticks);
if (print_details)
{
g_message (
"dwAbsPos: %d ", msg.dwAbsPos);
}
position_print (&pos);
position_from_ticks (
&global_pos,
r_obj->pos.total_ticks + ticks);
g_debug (
"dwAbsPos: %d ", msg.dwAbsPos);
if (ZRYTHM_HAVE_UI &&
pos.bars > TRANSPORT->total_bars -8)
@ -472,23 +471,20 @@ midi_region_new_from_midi_file ( @@ -472,23 +471,20 @@ midi_region_new_from_midi_file (
ev = msg.iType;
}
if (muGetMIDIMsgName (str, ev) &&
print_details)
if (muGetMIDIMsgName (str, ev))
{
g_message("MIDI msg name: %s", str);
g_debug ("MIDI msg name: %s", str);
}
switch(ev)
{
case msgNoteOff:
if (print_details)
{
g_message (
"Note off at %d "
"[ch %d pitch %d]",
msg.dwAbsPos,
msg.MsgData.NoteOff.iChannel,
msg.MsgData.NoteOff.iNote);
}
handle_note_off:
g_debug (
"Note off at %d "
"[ch %d pitch %d]",
msg.dwAbsPos,
msg.MsgData.NoteOff.iChannel,
msg.MsgData.NoteOff.iNote);
mn =
midi_region_pop_unended_note (
self, msg.MsgData.NoteOff.iNote);
@ -506,16 +502,23 @@ midi_region_new_from_midi_file ( @@ -506,16 +502,23 @@ midi_region_new_from_midi_file (
}
break;
case msgNoteOn:
if (print_details)
/* 0 velocity is a note off */
if (msg.MsgData.NoteOn.iVolume == 0)
{
g_message (
"Note on at %d "
"[ch %d pitch %d vel %d]",
msg.dwAbsPos,
msg.MsgData.NoteOn.iChannel,
msg.MsgData.NoteOn.iNote,
msg.MsgData.NoteOn.iVolume);
msg.MsgData.NoteOff.iChannel =
msg.MsgData.NoteOn.iChannel;
msg.MsgData.NoteOff.iNote =
msg.MsgData.NoteOn.iNote;
goto handle_note_off;
}
g_debug (
"Note on at %d "
"[ch %d pitch %d vel %d]",
msg.dwAbsPos,
msg.MsgData.NoteOn.iChannel,
msg.MsgData.NoteOn.iNote,
msg.MsgData.NoteOn.iVolume);
midi_region_start_unended_note (
self, &pos, NULL,
msg.MsgData.NoteOn.iNote,
@ -525,106 +528,76 @@ midi_region_new_from_midi_file ( @@ -525,106 +528,76 @@ midi_region_new_from_midi_file (
muGetNameFromNote (
str,
msg.MsgData.NoteKeyPressure.iNote);
if (print_details)
{
g_message (
"(%.2d) %s %d",
msg.MsgData.NoteKeyPressure.
iChannel,
str,
msg.MsgData.NoteKeyPressure.
iPressure);
}
g_debug (
"(%.2d) %s %d",
msg.MsgData.NoteKeyPressure.
iChannel,
str,
msg.MsgData.NoteKeyPressure.
iPressure);
break;
case msgSetParameter:
muGetControlName (
str,
msg.MsgData.NoteParameter.iControl);
if (print_details)
{
g_message (
"(%.2d) %s -> %d",
msg.MsgData.NoteParameter.iChannel,
str,
msg.MsgData.NoteParameter.iParam);
}
g_debug (
"(%.2d) %s -> %d",
msg.MsgData.NoteParameter.iChannel,
str,
msg.MsgData.NoteParameter.iParam);
break;
case msgSetProgram:
muGetInstrumentName (
str,
msg.MsgData.ChangeProgram.iProgram);
if (print_details)
{
g_message (
"(%.2d) %s",
msg.MsgData.ChangeProgram.iChannel,
str);
}
g_debug (
"(%.2d) %s",
msg.MsgData.ChangeProgram.iChannel,
str);
break;
case msgChangePressure:
muGetControlName (
str,
msg.MsgData.ChangePressure.
iPressure);
if (print_details)
{
g_message (
"(%.2d) %s",
msg.MsgData.ChangePressure.iChannel,
str);
}
g_debug (
"(%.2d) %s",
msg.MsgData.ChangePressure.iChannel,
str);
break;
case msgSetPitchWheel:
if (print_details)
{
g_message (
"(%.2d) %d",
msg.MsgData.PitchWheel.iChannel,
msg.MsgData.PitchWheel.iPitch);
}
g_debug (
"(%.2d) %d",
msg.MsgData.PitchWheel.iChannel,
msg.MsgData.PitchWheel.iPitch);
break;
case msgMetaEvent:
if (print_details)
{
g_message ("---- meta events");
}
g_debug ("---- meta events");
switch(msg.MsgData.MetaEvent.iType)
{
case metaMIDIPort:
if (print_details)
{
g_message (
"MIDI Port = %d",
msg.MsgData.MetaEvent.Data.
iMIDIPort);
}
g_debug (
"MIDI Port = %d",
msg.MsgData.MetaEvent.Data.
iMIDIPort);
break;
case metaSequenceNumber:
if (print_details)
{
g_message (
"Sequence Number = %d",
msg.MsgData.MetaEvent.Data.
iSequenceNumber);
}
g_debug (
"Sequence Number = %d",
msg.MsgData.MetaEvent.Data.
iSequenceNumber);
break;
case metaTextEvent:
if (print_details)
{
g_message (
"Text = '%s'",
msg.MsgData.MetaEvent.Data.Text.
pData);
}
g_debug (
"Text = '%s'",
msg.MsgData.MetaEvent.Data.Text.
pData);
break;
case metaCopyright:
if (print_details)
{
g_message (
"Copyright = '%s'",
msg.MsgData.MetaEvent.Data.Text.
pData);
}
g_debug (
"Copyright = '%s'",
msg.MsgData.MetaEvent.Data.Text.
pData);
break;
case metaTrackName:
{
@ -678,9 +651,9 @@ midi_region_new_from_midi_file ( @@ -678,9 +651,9 @@ midi_region_new_from_midi_file (
return NULL;
}
arranger_object_end_pos_setter (
(ArrangerObject *) self, &pos);
r_obj, &global_pos);
arranger_object_loop_end_pos_setter (
(ArrangerObject *) self, &pos);
r_obj, &global_pos);
break;
case metaSetTempo:
g_message (
@ -740,7 +713,7 @@ midi_region_new_from_midi_file ( @@ -740,7 +713,7 @@ midi_region_new_from_midi_file (
{
/* Already done a hex dump */
}
else if (print_details)
else
{
char txt[600];
char tmp[100];
@ -758,7 +731,7 @@ midi_region_new_from_midi_file ( @@ -758,7 +731,7 @@ midi_region_new_from_midi_file (
strcat (txt, tmp);
}
strcat (txt, "]");
g_message ("%s", txt);
g_debug ("%s", txt);
}
}
}
@ -766,6 +739,30 @@ midi_region_new_from_midi_file ( @@ -766,6 +739,30 @@ midi_region_new_from_midi_file (
midiReadFreeMessage (&msg);
midiFileClose (mf);
if (self->num_unended_notes != 0)
{
g_warning (
"unended notes found: %d",
self->num_unended_notes);
double length =
arranger_object_get_length_in_ticks (
(ArrangerObject *) self);
position_from_ticks (&end_pos, length);
while (self->num_unended_notes > 0)
{
mn =
midi_region_pop_unended_note (self, -1);
arranger_object_end_pos_setter (
(ArrangerObject *) mn, &end_pos);
}
}
g_return_val_if_fail (
position_is_before (
&self->base.pos, &self->base.end_pos), NULL);
g_message ("%s: done", __func__);
return self;
@ -1203,9 +1200,4 @@ midi_region_free_members (ZRegion * self) @@ -1203,9 +1200,4 @@ midi_region_free_members (ZRegion * self)
arranger_object_free (
(ArrangerObject *) self->midi_notes[i]);
}
for (int i = 0; i < self->num_unended_notes; i++)
{
arranger_object_free (
(ArrangerObject *) self->unended_notes[i]);
}
}

93
src/audio/tracklist.c

@ -21,6 +21,7 @@ @@ -21,6 +21,7 @@
#include "audio/audio_region.h"
#include "audio/channel.h"
#include "audio/chord_track.h"
#include "audio/midi_file.h"
#include "audio/router.h"
#include "audio/tracklist.h"
#include "audio/track.h"
@ -1006,14 +1007,35 @@ tracklist_handle_file_drop ( @@ -1006,14 +1007,35 @@ tracklist_handle_file_drop (
{
if (track_type == TRACK_TYPE_MIDI)
{
ui_show_error_message (
MAIN_WINDOW,
_("Cannot drop MIDI files into "
"existing tracks"));
goto free_file_and_return;
}
if (track->type != TRACK_TYPE_MIDI &&
track->type !=
TRACK_TYPE_INSTRUMENT)
{
ui_show_error_message (
MAIN_WINDOW,
_("Can only drop MIDI files on "
"MIDI/instrument tracks"));
goto free_file_and_return;
}
if (track_type == TRACK_TYPE_AUDIO &&
int num_nonempty_tracks =
midi_file_get_num_tracks (
file->abs_path, true);
if (num_nonempty_tracks > 1)
{
char msg[600];
sprintf (
msg,
_("This MIDI file contains %d "
"tracks. It cannot be dropped "
"into an existing track"),
num_nonempty_tracks);
ui_show_error_message (
MAIN_WINDOW, msg);
goto free_file_and_return;
}
}
else if (track_type == TRACK_TYPE_AUDIO &&
track->type != TRACK_TYPE_AUDIO)
{
ui_show_error_message (
@ -1023,30 +1045,53 @@ tracklist_handle_file_drop ( @@ -1023,30 +1045,53 @@ tracklist_handle_file_drop (
goto free_file_and_return;
}
/* create audio region in audio track */
int lane_pos =
lane ? lane->pos :
(track->num_lanes == 1 ?
0 : track->num_lanes - 2);
int idx_in_lane =
track->lanes[lane_pos]->num_regions;
ZRegion * region =
audio_region_new (
-1, file->abs_path, NULL, -1, NULL,
0, pos, track->pos, lane_pos,
idx_in_lane);
track_add_region (
track, region, NULL, lane_pos,
F_GEN_NAME, F_PUBLISH_EVENTS);
arranger_object_select (
(ArrangerObject *) region, F_SELECT,
F_NO_APPEND);
ZRegion * region = NULL;
switch (track_type)
{
case TRACK_TYPE_AUDIO:
/* create audio region in audio
* track */
region =
audio_region_new (
-1, file->abs_path, NULL, -1, NULL,
0, pos, track->pos, lane_pos,
idx_in_lane);
break;
case TRACK_TYPE_MIDI:
region =
midi_region_new_from_midi_file (
pos, file->abs_path, track->pos,
lane_pos, idx_in_lane, 0);
break;
default:
break;
}
UndoableAction * ua =
arranger_selections_action_new_create (
TL_SELECTIONS);
undo_manager_perform (
UNDO_MANAGER, ua);
if (region)
{
track_add_region (
track, region, NULL, lane_pos,
F_GEN_NAME, F_PUBLISH_EVENTS);
arranger_object_select (
(ArrangerObject *) region,
F_SELECT,
F_NO_APPEND);
UndoableAction * ua =
arranger_selections_action_new_create (
TL_SELECTIONS);
undo_manager_perform (
UNDO_MANAGER, ua);
}
else
{
g_warn_if_reached ();
}
goto free_file_and_return;
}

37
tests/actions/tracklist_selections.c

@ -57,6 +57,17 @@ test_num_tracks_with_file ( @@ -57,6 +57,17 @@ test_num_tracks_with_file (
undo_manager_perform (
UNDO_MANAGER, ua);
Track * first_track =
TRACKLIST->tracks[num_tracks_before];
track_select (
first_track, F_SELECT, F_EXCLUSIVE,
F_NO_PUBLISH_EVENTS);
ua =
tracklist_selections_action_new_delete (
TRACKLIST_SELECTIONS);
undo_manager_perform (UNDO_MANAGER, ua);
undo_manager_undo (UNDO_MANAGER);
g_assert_cmpint (
TRACKLIST->num_tracks, ==,
num_tracks_before + num_tracks);
@ -1476,6 +1487,29 @@ test_multi_track_duplicate (void) @@ -1476,6 +1487,29 @@ test_multi_track_duplicate (void)
test_helper_zrythm_cleanup ();
}
static void
test_delete_track_w_midi_file (void)
{
test_helper_zrythm_init ();
char * midi_file =
g_build_filename (
TESTS_SRCDIR,
"format_1_two_tracks_with_data.mid",
NULL);
test_num_tracks_with_file (midi_file, 2);
g_free (midi_file);
g_assert_cmpint (
TRACKLIST->tracks[TRACKLIST->num_tracks - 2]->
lanes[0]->regions[0]->num_midi_notes, ==, 7);
g_assert_cmpint (
TRACKLIST->tracks[TRACKLIST->num_tracks - 1]->
lanes[0]->regions[0]->num_midi_notes, ==, 6);
test_helper_zrythm_cleanup ();
}
int
main (int argc, char *argv[])
{
@ -1538,6 +1572,9 @@ main (int argc, char *argv[]) @@ -1538,6 +1572,9 @@ main (int argc, char *argv[])
g_test_add_func (
TEST_PREFIX "test multi track duplicate",
(GTestFunc) test_multi_track_duplicate);
g_test_add_func (
TEST_PREFIX "test delete track w midi file",
(GTestFunc) test_delete_track_w_midi_file);
return g_test_run ();
}

BIN
tests/format_1_two_tracks_with_data.mid

Binary file not shown.

2
tests/meson.build

@ -37,6 +37,8 @@ if get_option ('tests') @@ -37,6 +37,8 @@ if get_option ('tests')
test_env.set ('VST3_PATH', '/tmp/zrythm_vst3')
test_env.set ('LADSPA_PATH', '/tmp/zrythm_ladspa')
test_env.set ('DSSI_PATH', '/tmp/zrythm_dssi')
test_env.set ('ZRYTHM_DEBUG', '1')
test_env.set ('G_MESSAGES_DEBUG', 'zrythm')
test_config = configuration_data ()
test_config.set_quoted (

Loading…
Cancel
Save