-
Notifications
You must be signed in to change notification settings - Fork 0
/
pivot_note_on_off.R
162 lines (142 loc) · 5.33 KB
/
pivot_note_on_off.R
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
#' Write \code{note_on} and \code{note_off} events in the same line (long to wide)
#'
#' @param df_measures data.frame resulting of miditapyr$mido_midi_df() and then running tab_measures() (see example)
#'
#' @return A data.frame with the following columns pivoted to wide: c("m", "b", "t", "ticks", "time", "velocity"). Every column is pivoted to wide with the suffix "_note_on" & "_note_off" showing the values of the original column as a prefix. See \code{?tab_measures} for an explanation of the meaning of these columns.
#' @family Pivot midi frame functions
#'
#' @export
#'
#' @example man/rmdhunks/examples/generate_unnested_df.Rmd
#' @examples
#' \dontrun{
#' dfm <- tab_measures(df, ticks_per_beat)
#' library(zeallot)
#' c(df_meta, df_notes) %<-% miditapyr$split_df(dfm)
#' df_notes %>% pivot_wide_notes()
#' }
pivot_wide_notes <- function(df_measures) {
values_from_vector <-
dplyr::intersect(
names(df_measures),
c("m", "b", "t", "ticks", "time", "velocity")
)
df_measures %>%
# dplyr::select(c("i_track", "channel", "type", "m", "b", "t", "ticks", "time", "note", "velocity", "i_note")) %>%
# dplyr::select(c("i_track", "name", "channel", "type", "m", "b", "t", "ticks", "time", "note", "velocity", "i_note")) %>%
tidyr::pivot_wider(names_from = c("type"), values_from = !!values_from_vector) %>%
# as.data.frame() %>%
dplyr::arrange(i_track, b_note_on)
}
#' Write \code{note_on} and \code{note_off} events in two lines (wide to long)
#'
#' @param df_notes_wide notes dataframe in wide format
#'
#' @return Transforms notes in wide dataframe format to long format.
#' @family Pivot midi frame functions
#' @export
#'
#' @example man/rmdhunks/examples/generate_unnested_df.Rmd
#' @examples
#' \dontrun{
#' dfm <- tab_measures(df, ticks_per_beat)
#' library(zeallot)
#' c(df_meta, df_notes) %<-% miditapyr$split_df(dfm)
#' dfw <- df_notes %>% pivot_wide_notes()
#' dfw %>% pivot_long_notes()
#' }
pivot_long_notes <- function(df_notes_wide) {
if (is.null(df_notes_wide)) {
return(NULL)
}
df_notes_wide %>%
dplyr::select(
c("i_track", "channel", "note", "i_note"),
dplyr::matches("_note_o[nf]f?$")
) %>%
tidyr::pivot_longer(
dplyr::matches("_note_o[nf]f?$"),
names_to = c(".value", "type"),
names_pattern = "(.+?)_(.*)"
) %>%
dplyr::mutate(meta = FALSE)
}
#' Merge dataframes transformed back to long format
#'
#' Merge dataframes transformed back to long format, remove added columns and
#' transform to the right chronological order in order to replace the original
#' midi_frame_unnested object.
#'
#' @param df_meta,df_notes_long,df_not_notes Dataframes in the format of \code{split_midi_frame()}.
#'
#' @return Merges the input dataframes, arranges by \code{i_track} & (absolute) \code{ticks}, and
#' calculates \code{time} (in relative ticks since the last event).
#' @export
#' @family Split/merge meta/notes/non-note events
#'
#' @examples
#' \dontrun{
#' midi_file_string <- system.file("extdata", "test_midi_file.mid", package = "pyramidi")
#' mf <- miditapyr$MidiFrames(midi_file_string)
#'
#' dfm <- tab_measures(mf$midi_frame_unnested$df, ticks_per_beat = mf$midi_file$ticks_per_beat)
#'
#' l <- split_midi_frame(dfm)
#'
#' df_notes_long <- pivot_long_notes(l$df_notes_wide)
#'
#' merge_midi_frames(l$df_meta, df_notes_long, l$df_not_notes)
#' }
merge_midi_frames <- function(df_meta, df_notes_long, df_not_notes) {
if (is.null(df_meta) & is.null(df_notes_long) & is.null(df_not_notes)) {
return(NULL)
}
cols_to_remove <- c("i_note", "ticks", "t", "m", "b")
res <- df_notes_long %>%
dplyr::bind_rows(df_meta) %>%
dplyr::bind_rows(df_not_notes)
# if in tab_measures() there weren't all columns built, we have to remove them here:
cols_to_remove <- intersect(cols_to_remove, names(res))
res %>%
dplyr::arrange(i_track, ticks) %>%
dplyr::group_by(i_track) %>%
dplyr::mutate(time = ticks - dplyr::lag(ticks) %>% {.[1] = 0; .}) %>%
dplyr::ungroup() %>%
dplyr::select(-!!cols_to_remove)
}
#' Split unnested midi dataframe into parts
#'
#' Unnested midi frame with time data from \code{tab_measures()} is split into 3 parts:
#' \itemize{
#' \item{df_meta: Consisting of all the \href{https://mido.readthedocs.io/en/latest/midi_files.html#meta-messages}{meta} events in the midi data.}
#' \item{df_not_notes: Containing all midi events that are not \code{meta} and not \code{note_on} or \code{note_off}.}
#' \item{df_notes_wide: All \code{note_on} or \code{note_off} events and furthermore transformed to wide format by \code{pivot_note_wide}.}
#' }
#'
#' @param dfm result of \code{tab_measures()}
#'
#' @return df_meta, df_not_notes & df_notes_wide
#' @export
#' @family Split/merge meta/notes/non-note events
#'
#' @example man/rmdhunks/examples/generate_unnested_df.Rmd
#' @examples
#' \dontrun{
#' dfm <- tab_measures(df, ticks_per_beat)
#' split_midi_frame(dfm)
#' }
split_midi_frame <- function(dfm) {
if (is.null(dfm)) {
return(list(NULL, NULL, NULL))
}
# c(df_meta, df_notes) %<-% miditapyr$split_df(dfm)
l <- miditapyr$split_df(dfm)
df_meta <- l[[1]]
df_notes <- l[[2]]
df_not_notes <- df_notes %>%
dplyr::filter(!stringr::str_detect(type, "^note_o[nf]f?$"))
df_notes_wide <- df_notes %>%
dplyr::filter(stringr::str_detect(type, "^note_o[nf]f?$")) %>%
pivot_wide_notes()
tibble::lst(df_meta, df_not_notes, df_notes_wide)
}