This vignette shows how a midi file can also be read in by the tuneR package and then written to disc. However, for other midi files the code might need to be adapted a bit.
Read midi in unnested format
pyramidi
With pyramidi data can be loaded into a unnested dataframe like this:
mid_file_str <- system.file("extdata", "test_midi_file.mid", package = "pyramidi")
mido_mid_file <- mido$MidiFile(mid_file_str)
#> Error in python_config_impl(python) :
#> Error running '/home/runner/.virtualenvs/r-reticulate/python': No such file.
#> The Python installation used to create the virtualenv has been moved or removed:
#> '/opt/hostedtoolcache/Python/3.11.8/x64/bin'
dfc <- miditapyr$frame_midi(mido_mid_file)
ticks_per_beat = mido_mid_file$ticks_per_beat
df <- dfc %>%
miditapyr$unnest_midi() %>%
as_tibble()
df
#> # A tibble: 268 × 13
#> i_track meta type name time note velocity channel tempo numerator
#> <dbl> <lgl> <chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 0 TRUE track_name drum… 0 NaN NaN NaN NaN NaN
#> 2 0 FALSE note_on NA 0 43 72 9 NaN NaN
#> 3 0 FALSE note_on NA 0 39 64 9 NaN NaN
#> 4 0 FALSE note_on NA 0 36 101 9 NaN NaN
#> 5 0 TRUE set_tempo NA 0 NaN NaN NaN 666666 NaN
#> 6 0 TRUE time_signa… NA 0 NaN NaN NaN NaN 4
#> 7 0 FALSE note_off NA 240 43 72 9 NaN NaN
#> 8 0 FALSE note_off NA 0 39 64 9 NaN NaN
#> 9 0 FALSE note_off NA 0 36 101 9 NaN NaN
#> 10 0 FALSE note_on NA 240 42 101 9 NaN NaN
#> # ℹ 258 more rows
#> # ℹ 3 more variables: denominator <dbl>, clocks_per_click <dbl>,
#> # notated_32nd_notes_per_beat <dbl>
tuneR
With tuneR this can be done for this file in the following way:
df_tuner <- tuneR::readMidi(mid_file_str)
(for other midi files the cleaning code would have to be adapted)
Changing format in tuneR dataframe
- to make compatible with pyramidi
# vector according to which we'll
# rename the event types to the names given by mido, cf.:
# https://mido.readthedocs.io/en/latest/message_types.html &
# https://mido.readthedocs.io/en/latest/meta_message_types.html
rename_type_vec <-
c(
"Set Tempo" = "set_tempo",
"Time Signature" = "time_signature",
"Note On" = "note_on",
"Note Off" = "note_off",
"Sequence/Track Name" = "track_name",
"End of Track" = "end_of_track",
"Key Signature" = "key",
"Controller" = "control",
"Program Change" = "program",
"Pitch Bend" = "pitch"
)
# transform the tuneR data.frame to the pyramidi format :
df_tuner_mod <- df_tuner %>%
mutate(event = as.character(event)) %>%
as_tibble() %>%
mutate(meta = !is.na(type)) %>%
select(-type) %>%
rename(type = event,
note = parameter1,
velocity = parameter2,
i_track = track) %>%
mutate(type = recode(type, !!!rename_type_vec)) %>%
mutate(tempo = ifelse(type == "set_tempo", as.integer(parameterMetaSystem), NA)) %>%
mutate(name = ifelse(type == "track_name", parameterMetaSystem, NA)) %>%
mutate(temp = ifelse(type == "time_signature", parameterMetaSystem, NA)) %>%
separate(temp, c("numerator",
"denominator",
"clocks_per_click",
"temp1", "temp2",
"notated_32nd_notes_per_beat"),
convert = TRUE) %>%
select(-temp1, -temp2) %>%
arrange(i_track, time) %>%
group_by(i_track) %>%
# calculate time increments instead of total time:
mutate(time = time - lag(time) %>% {.[1] = 0; .}) %>%
ungroup() %>%
mutate_if(is.numeric, ~ifelse(is.na(.), NaN, .)) %>%
mutate(name = ifelse(is.na(name), list(NULL), name)) %>%
# small correction of transforming "NA" strings into real NA values:
mutate(name = ifelse(name == "NA", NA, name)) %>%
select(-parameterMetaSystem)
#> Warning: There was 1 warning in `mutate()`.
#> ℹ In argument: `tempo = ifelse(...)`.
#> Caused by warning in `ifelse()`:
#> ! NAs introduced by coercion
#> Warning: Expected 6 pieces. Additional pieces discarded in 1 rows [6].
Translate to pyramidi format
dfm <- tab_measures(df_tuner_mod, ticks_per_beat)
dfm %>%
split_midi_frame() %->%
c(df_meta, df_not_notes, df_notes_wide)
df_meta <-
df_meta
Export midi file from tuneR data
Now we have the data in 3 objects that MidiFramer
can
understand. First let’s create an empty MidiFramer
object:
mfr <- MidiFramer$new()
We’ll set the ticks per beat (see here)
mfr$ticks_per_beat <- ticks_per_beat
First we’ll pass the first two dataframes in the according fields of
mfr
:
mfr$df_meta <- df_meta
mfr$df_not_notes <- df_not_notes
We’ll add the last one with the method
MidiFramer$update_notes_wide()
, in order to pass the
information to other fields (in particular
mfr$mf$midi_frame_nested
, which can be transformed back to
a midi file; see vignette("package_workflow")
):
mfr$update_notes_wide(df_notes_wide)
mfr$mf$write_file("round_tripped_midi_file.mid")