#' A page builder for creating a specified number of play_melody_loops
#'
#' @param item_bank
#' @param presampled_items
#' @param num_items
#' @param var_name
#' @param stimuli_type
#' @param page_type
#' @param max_goes
#' @param page_title
#' @param page_text
#' @param get_answer
#' @param rel_to_abs_mel_function
#' @param start_from_trial_no
#' @param clip_stimuli_length
#' @param arrhythmic
#' @param example
#' @param feedback
#' @param sound
#' @param get_trial_characteristics_function
#' @param max_goes_forced
#' @param display_modality
#' @param show_progress
#' @param sheet_music_start_hidden
#' @param sound_only_first_melody_note
#' @param sheet_music_id
#' @param give_first_melody_note
#' @param get_similarity_to_previous_melody
#' @param volume_meter
#' @param volume_meter_type
#' @param melody_block_paradigm
#' @param singing_trials
#' @param phase
#' @param melody_trial_paradigm
#' @param first_note_message
#' @param transposed_message
#' @param play_first_note_button_text
#' @param learn_test_paradigm
#' @param sample_item_bank_via_api
#' @param pass_items_through_url_parameter
#' @param mute_midi_playback
#'
#' @return
#' @export
#'
#' @examples
multi_page_play_melody_loop <- function(item_bank = NULL,
presampled_items = NULL,
num_items = NULL, # Can be null if presampled
var_name = "melody",
stimuli_type = "midi_notes",
page_type = "record_audio_page",
max_goes = 4L,
page_title = psychTestR::i18n("copy_melody_title"),
page_text = "Press play to hear the melody, then play it back as best as you can when it finishes.",
get_answer = get_answer_pyin_melodic_production,
rel_to_abs_mel_function = NULL,
start_from_trial_no = 1L,
clip_stimuli_length = FALSE,
arrhythmic = FALSE,
example = FALSE,
feedback = FALSE,
sound = "piano",
get_trial_characteristics_function = NULL,
max_goes_forced = FALSE,
display_modality = "auditory",
show_progress = TRUE,
sheet_music_start_hidden = FALSE,
sound_only_first_melody_note = FALSE,
sheet_music_id = 'sheet_music',
give_first_melody_note = FALSE,
get_similarity_to_previous_melody = FALSE,
volume_meter = FALSE,
volume_meter_type = 'default',
melody_block_paradigm = c('standard', 'sing_melody_first', 'learn_phase_visual_display_modality'),
singing_trials = TRUE,
phase = c('test', 'learn', 'review', 'example'),
melody_trial_paradigm = c("call_and_response", "simultaneous_recall"),
first_note_message = psychTestR::i18n("first_note_is"),
transposed_message = psychTestR::i18n("transposed"),
play_first_note_button_text = psychTestR::i18n("play_first_note"),
learn_test_paradigm = FALSE,
sample_item_bank_via_api = FALSE,
pass_items_through_url_parameter = FALSE,
mute_midi_playback = FALSE) {
melody_block_paradigm <- match.arg(melody_block_paradigm)
melody_trial_paradigm <- match.arg(melody_trial_paradigm)
phase <- match.arg(phase)
stopifnot(is.null.or(item_bank, tibble::is_tibble),
is.null.or(presampled_items, is.data.frame),
is.null.or(num_items, is.scalar.numeric),
is.scalar.character(var_name),
is.scalar.character(stimuli_type),
is.scalar.character(page_type),
is.scalar.numeric(max_goes),
is(page_title, "html") || is.character(page_title),
is.scalar.character(page_text) || is(page_text, "shiny.tag"),
is.function(get_answer),
is.null.or(rel_to_abs_mel_function, is.function),
is.scalar.numeric(start_from_trial_no),
is.scalar.logical(clip_stimuli_length),
is.scalar.logical(arrhythmic),
is.scalar.logical(example),
is.function(feedback) || is.scalar.logical(feedback), # Technically, it could still be a logical here
is.scalar.character(sound),
is.null.or(get_trial_characteristics_function, is.function),
is.scalar.logical(max_goes_forced),
is.scalar.character(display_modality),
is.scalar.logical(show_progress),
is.scalar.logical(sheet_music_start_hidden),
is.scalar.logical(sound_only_first_melody_note),
is.scalar.character(sheet_music_id),
is.scalar.logical(give_first_melody_note),
is.scalar.logical(get_similarity_to_previous_melody),
is.scalar.logical(volume_meter),
is.scalar.character(volume_meter_type),
melody_block_paradigm %in% c('standard', 'sing_melody_first', 'learn_phase_visual_display_modality'),
is.scalar.logical(singing_trials),
phase %in% c('learn', 'test', 'review', 'example'),
melody_trial_paradigm %in% c("call_and_response", "simultaneous_recall"),
is.scalar.character(first_note_message),
is.scalar.character(transposed_message),
is.scalar.character(play_first_note_button_text),
is.scalar.logical(learn_test_paradigm),
is.scalar.logical(sample_item_bank_via_api),
is.scalar.logical(pass_items_through_url_parameter),
is.scalar.logical(mute_midi_playback)
)
if(is.null(presampled_items) && is.infinite(num_items)) {
# Then the test stops when defined by the user
items <- psychTestR::join(
psychTestR::code_block(function(state, ...) {
logging::loginfo("No presampled items and infinite items (user-determined stop)")
psychTestR::set_global("reactive_melody_no", 1L, state)
psychTestR::set_global("user_determined_stop", FALSE, state)
}),
psychTestR::while_loop(test = function(state, ...) {
if(!is.null(get_trial_characteristics_function)) {
var_name <- paste0(var_name, "_trial_characteristics")
}
trials <- psychTestR::get_global(var_name, state)
if(length(trials) == 0) {
logging::loginfo("trials is NULL for var_name %s. Exiting while_loop.", var_name)
return(FALSE)
}
logging::loginfo("user_determined_stop? %s", psychTestR::get_global("user_determined_stop", state))
continue <- (! psychTestR::get_global("user_determined_stop", state)) && nrow(trials) > 0L
logging::loginfo("continue? %s", continue)
continue
}, logic = psychTestR::join(
construct_play_melody_page(melody = NULL,
melody_row = NULL,
melody_no = NA,
var_name,
max_goes,
page_type,
page_title,
page_text,
get_answer,
stimuli_type,
rel_to_abs_mel_function,
clip_stimuli_length,
arrhythmic,
example,
sound,
get_trial_characteristics_function,
max_goes_forced,
item_bank,
display_modality,
num_items,
show_progress,
sheet_music_start_hidden,
sound_only_first_melody_note,
sheet_music_id,
give_first_melody_note,
get_similarity_to_previous_melody,
volume_meter,
volume_meter_type,
singing_trials,
phase,
melody_block_paradigm,
melody_trial_paradigm,
first_note_message,
transposed_message,
play_first_note_button_text,
reactive_melody_no = TRUE,
learn_test_paradigm,
sample_item_bank_via_api,
pass_items_through_url_parameter = pass_items_through_url_parameter,
feedback = feedback,
mute_midi_playback = mute_midi_playback),
psychTestR::conditional(function(state, ...) {
if(!is.null(get_trial_characteristics_function)) {
var_name <- paste0(var_name, "_trial_characteristics")
}
cond <- nrow(psychTestR::get_global(var_name, state)) > 0L
logging::loginfo("cond?: %s", cond)
if(!cond) {
psychTestR::set_global("user_determined_stop", TRUE, state)
}
cond
}, psychTestR::NAFC_page(label = "finished_user_determined_loop",
choices = c(psychTestR::i18n("Yes"), psychTestR::i18n("No")),
prompt = psychTestR::i18n("continue_learning_question"),
on_complete = function(state, answer, ...) {
if(answer %in% c("No", "Nein")) {
psychTestR::set_global("user_determined_stop", TRUE, state)
reactive_melody_no <- psychTestR::get_global("reactive_melody_no", state)
psychTestR::set_global("reactive_melody_no", reactive_melody_no + 1L, state)
}
}))
)
)
)
} else if(is.null(presampled_items) && ! is.infinite(num_items)) {
if(sample_item_bank_via_api) {
start_from_trial_no <- 1L # We apply this a lower case
}
# This will return a sequence of test items
items <- purrr::map(start_from_trial_no:num_items, function(melody_no) {
construct_play_melody_page(melody = NULL,
melody_row = NULL,
melody_no,
var_name,
max_goes,
page_type,
page_title,
page_text,
get_answer,
stimuli_type,
rel_to_abs_mel_function,
clip_stimuli_length,
arrhythmic,
example,
sound,
get_trial_characteristics_function,
max_goes_forced,
item_bank,
display_modality,
num_items,
show_progress,
sheet_music_start_hidden,
sound_only_first_melody_note,
sheet_music_id,
give_first_melody_note,
get_similarity_to_previous_melody,
volume_meter,
volume_meter_type,
singing_trials,
phase,
melody_block_paradigm,
melody_trial_paradigm,
first_note_message,
transposed_message,
play_first_note_button_text,
reactive_melody_no = FALSE,
learn_test_paradigm,
sample_item_bank_via_api,
if(sample_item_bank_via_api) start_from_trial_no else NULL,
pass_items_through_url_parameter = pass_items_through_url_parameter,
feedback = feedback,
mute_midi_playback = mute_midi_playback)
})
} else {
items <- purrr::map(1:nrow(presampled_items), function(x) {
melody <- presampled_items %>% dplyr::slice(!! x)
construct_play_melody_page(melody = NULL,
melody_row = melody, # We actually want to give the whole row to melody row
melody_no = x,
var_name,
max_goes,
page_type,
page_title,
page_text,
get_answer,
stimuli_type,
rel_to_abs_mel_function ,
clip_stimuli_length,
arrhythmic,
example,
sound,
get_trial_characteristics_function,
max_goes_forced,
item_bank,
display_modality,
num_items,
show_progress,
sheet_music_start_hidden,
sound_only_first_melody_note,
sheet_music_id,
give_first_melody_note,
get_similarity_to_previous_melody,
volume_meter,
volume_meter_type,
singing_trials,
phase,
melody_block_paradigm,
melody_trial_paradigm,
first_note_message,
transposed_message,
play_first_note_button_text,
reactive_melody_no = FALSE,
learn_test_paradigm,
sample_item_bank_via_api,
pass_items_through_url_parameter = pass_items_through_url_parameter,
feedback = feedback,
mute_midi_playback = mute_midi_playback)
})
}
# Potentially unnest
if(purrr::some(items, is.list)) {
items <- items %>% unlist()
}
psychTestR::join(
psychTestR::code_block(function(state, ...) {
psychTestR::set_local("presampled_items", presampled_items, state)
}),
items
)
}
construct_play_melody_page <- function(melody = NULL,
melody_row = NULL,
melody_no,
var_name,
max_goes,
page_type,
page_title,
page_text,
get_answer,
stimuli_type,
rel_to_abs_mel_function,
clip_stimuli_length,
arrhythmic,
example,
sound,
get_trial_characteristics_function,
max_goes_forced,
item_bank,
display_modality,
num_items,
show_progress,
sheet_music_start_hidden,
sound_only_first_melody_note,
sheet_music_id,
give_first_melody_note,
get_similarity_to_previous_melody,
volume_meter,
volume_meter_type,
singing_trials,
phase,
melody_block_paradigm,
melody_trial_paradigm,
first_note_message,
transposed_message,
play_first_note_button_text,
reactive_melody_no = FALSE,
learn_test_paradigm = FALSE,
sample_item_bank_via_api = FALSE,
start_from_trial_no = NULL,
pass_items_through_url_parameter = FALSE,
feedback = FALSE,
mute_midi_playback = FALSE) {
if(!singing_trials) {
page_text <- shiny::tags$div(
shiny::tags$img(src = "https://musicassessr.com/assets/img/saxophone.png", height = 100, width = 100),
shiny::tags$br(),
page_text
)
}
page <- play_melody_loop(melody_no = melody_no,
melody_row = melody_row,
melody = melody,
var_name = var_name,
max_goes = max_goes,
page_type = page_type,
page_title = page_title,
page_text = page_text,
get_answer = get_answer,
stimuli_type = stimuli_type,
rel_to_abs_mel_function = rel_to_abs_mel_function,
clip_stimuli_length = clip_stimuli_length,
arrhythmic = arrhythmic,
example = example,
sound = sound,
get_trial_characteristics_function = get_trial_characteristics_function,
max_goes_forced = max_goes_forced,
item_bank = item_bank,
display_modality = display_modality,
total_no_melodies = num_items,
show_progress = show_progress,
sheet_music_start_hidden = sheet_music_start_hidden,
sound_only_first_melody_note = sound_only_first_melody_note,
sheet_music_id = sheet_music_id,
give_first_melody_note = give_first_melody_note,
get_similarity_to_previous_melody = get_similarity_to_previous_melody,
volume_meter = volume_meter,
volume_meter_type = volume_meter_type,
singing_trial = singing_trials,
phase = phase,
melody_block_paradigm = melody_block_paradigm,
melody_trial_paradigm = melody_trial_paradigm,
first_note_message = first_note_message,
transposed_message = transposed_message,
play_first_note_button_text = play_first_note_button_text,
skip_sampling_and_take_from_last_melody = melody_block_paradigm == "sing_melody_first",
reactive_melody_no = reactive_melody_no,
learn_test_paradigm = learn_test_paradigm,
sample_item_bank_via_api = sample_item_bank_via_api,
start_from_trial_no = start_from_trial_no,
pass_items_through_url_parameter = pass_items_through_url_parameter,
feedback = feedback,
mute_midi_playback = mute_midi_playback)
# In the case of putting a sing melody page first, we do the sampling on the sing page (in code below, but which chronogically comes first); then we skip sampling on the "real" (instrument) version, and just use the sampled melody from the sing page
if (melody_block_paradigm == "sing_melody_first") {
sing_page <- play_melody_loop(melody_no = melody_no,
var_name = var_name,
max_goes = max_goes,
page_type = "record_audio_page",
page_title = psychTestR::i18n("Sing_the_Melody"),
page_text = shiny::tags$div(
shiny::tags$img(id = "singImage", src = "https://musicassessr.com/assets/img/singing.png", height = 100, width = 100),
shiny::tags$br(),
psychTestR::i18n("sing_melody_page_text")
),
get_answer = get_answer,
stimuli_type = stimuli_type,
rel_to_abs_mel_function = rel_to_abs_mel_function,
clip_stimuli_length = clip_stimuli_length,
arrhythmic = arrhythmic,
example = example,
sound = sound,
get_trial_characteristics_function = get_trial_characteristics_function,
max_goes_forced = max_goes_forced,
item_bank = item_bank,
display_modality = "auditory",
total_no_melodies = num_items,
show_progress = show_progress,
sheet_music_start_hidden = sheet_music_start_hidden,
sound_only_first_melody_note = FALSE,
sheet_music_id = sheet_music_id,
give_first_melody_note = FALSE,
get_similarity_to_previous_melody = get_similarity_to_previous_melody,
volume_meter = volume_meter,
volume_meter_type = volume_meter_type,
singing_trial = TRUE,
phase = phase,
melody_block_paradigm = melody_block_paradigm,
melody_trial_paradigm = melody_trial_paradigm,
first_note_message = first_note_message,
transposed_message = transposed_message,
play_first_note_button_text = play_first_note_button_text,
reactive_melody_no = reactive_melody_no,
learn_test_paradigm = learn_test_paradigm,
sample_item_bank_via_api = sample_item_bank_via_api,
start_from_trial_no = start_from_trial_no,
pass_items_through_url_parameter = pass_items_through_url_parameter,
feedback = feedback,
mute_midi_playback = mute_midi_playback)
sing_then_play_pages <- psychTestR::join(psychTestR::one_button_page(shiny::tags$p(psychTestR::i18n("Now_you_will"), " ", shiny::tags$strong(psychTestR::i18n("sing")), " ", psychTestR::i18n("you_the_melody"))),
sing_page,
psychTestR::one_button_page(shiny::tags$p(psychTestR::i18n("Now_you_will"), " ", shiny::tags$strong(psychTestR::i18n("play")), " ", psychTestR::i18n("the_melody_on_your_instrument"))),
page)
return(sing_then_play_pages)
} else {
return(page)
}
}
#' Create a psychTestR test loop for having several attempts at playing back a melody.
#'
#' @param item_bank
#' @param melody
#' @param melody_no
#' @param var_name
#' @param stimuli_type
#' @param max_goes
#' @param max_goes_forced
#' @param page_type
#' @param page_title
#' @param page_text
#' @param answer_meta_data
#' @param get_answer
#' @param rel_to_abs_mel_function
#' @param clip_stimuli_length
#' @param start_note
#' @param end_note
#' @param durations
#' @param arrhythmic
#' @param note_length
#' @param play_button_text
#' @param example
#' @param sound
#' @param reactive_stimuli
#' @param get_trial_characteristics_function
#' @param display_modality
#' @param total_no_melodies
#' @param show_progress
#' @param sheet_music_start_hidden
#' @param sound_only_first_melody_note
#' @param sheet_music_id
#' @param give_first_melody_note
#' @param psychTestRCAT
#' @param get_similarity_to_previous_melody
#' @param volume_meter
#' @param volume_meter_type
#' @param singing_trial Boolean: if TRUE, the trial is defined as singing-based, otherwise instrument-based.
#' @param phase Can be one of 'learn', 'test', 'review' or 'example'
#' @param melody_block_paradigm
#' @param melody_trial_paradigm
#' @param first_note_message
#' @param transposed_message
#' @param play_first_note_button_text
#' @param skip_sampling_and_take_from_last_melody
#' @param reactive_melody_no
#' @param learn_test_paradigm
#' @param sample_item_bank_via_api
#' @param pass_items_through_url_parameter
#' @param feedback
#' @param mute_midi_playback
#'
#'
#' @return
#' @export
#'
#' @examples
play_melody_loop <- function(item_bank = NULL,
melody = NULL,
melody_no = 0,
var_name = "melody",
stimuli_type = "midi_notes",
max_goes = 4L,
max_goes_forced = FALSE,
page_type = "record_audio_page",
page_title = "Copy The Melody",
page_text = "Press play to hear the melody, then play it back as best as you can when it finishes.",
answer_meta_data = data.frame(),
get_answer = get_answer_pyin_melodic_production,
rel_to_abs_mel_function = NULL,
clip_stimuli_length = FALSE,
start_note = 1L,
end_note = "end",
durations = 'null',
arrhythmic = FALSE,
note_length = 0.5,
play_button_text = psychTestR::i18n("Play"),
example = FALSE,
sound = "piano",
reactive_stimuli = NULL,
get_trial_characteristics_function = NULL,
display_modality = "auditory",
total_no_melodies = 0,
show_progress = TRUE,
sheet_music_start_hidden = FALSE,
sound_only_first_melody_note = FALSE,
sheet_music_id = 'sheet_music',
give_first_melody_note = FALSE,
psychTestRCAT = FALSE,
melody_row = NULL,
get_similarity_to_previous_melody = FALSE,
volume_meter = FALSE,
volume_meter_type = 'default',
singing_trial = TRUE,
phase = c('test', 'learn', 'review', 'example'),
melody_block_paradigm = c('standard', 'sing_melody_first', 'learn_phase_visual_display_modality'),
melody_trial_paradigm = c("call_and_response", "simultaneous_recall"),
first_note_message = psychTestR::i18n("first_note_is"),
transposed_message = psychTestR::i18n("transposed"),
play_first_note_button_text = psychTestR::i18n("play_first_note"),
skip_sampling_and_take_from_last_melody = FALSE,
reactive_melody_no = FALSE,
learn_test_paradigm = FALSE,
sample_item_bank_via_api = FALSE,
start_from_trial_no = NULL,
pass_items_through_url_parameter = FALSE,
feedback = FALSE,
mute_midi_playback = FALSE) {
save_answer <- ! example
phase <- match.arg(phase)
melody_block_paradigm <- match.arg(melody_block_paradigm)
melody_trial_paradigm <- match.arg(melody_trial_paradigm)
psychTestR::join(
# Set the user satisfied state to false
psychTestR::code_block(function(state, ...) {
logging::loginfo("Start play_melody_loop")
logging::loginfo("melody: %s", melody)
# Repeat melody logic stuff
psychTestR::set_global("user_satisfied", "Try Again", state)
psychTestR::set_global("number_attempts", 1, state)
psychTestR::set_global("max_goes", max_goes, state)
psychTestR::set_global("attempts_left", max_goes, state)
psychTestR::set_global("display_modality", display_modality, state)
psychTestR::set_global("phase", phase, state)
psychTestR::set_global("rhythmic", ! arrhythmic, state)
psychTestR::set_global("melody_block_paradigm", melody_block_paradigm, state)
# Grab sampled melody for this trial (if one not specified)
if(is.null(melody) && ! skip_sampling_and_take_from_last_melody && phase != "review") grab_sampled_melody(item_bank, melody_row, var_name, stimuli_type, state, melody_no, arrhythmic, rel_to_abs_mel_function, note_length, get_trial_characteristics_function, psychTestRCAT, get_similarity_to_previous_melody, phase, display_modality, reactive_melody_no, learn_test_paradigm, sample_item_bank_via_api, start_from_trial_no, pass_items_through_url_parameter)
if(phase == "review") grab_sampled_melody_review(var_name, state, melody_no, arrhythmic, rel_to_abs_mel_function, note_length, pass_items_through_url_parameter)
}),
# Keep in loop until the participant confirms they are happy with their entry
psychTestR::while_loop(test = function(state, ...) {
logging::loginfo("Does the user want to play again?")
number_attempts <- psychTestR::get_global("number_attempts", state)
user_answer <- psychTestR::get_global("user_satisfied", state)
user_wants_to_play_again <- user_answer %in% dict_key_to_translations("Try_Again")
logging::loginfo("user_wants_to_play_again? %s", user_wants_to_play_again)
user_wants_to_play_again
},
logic = psychTestR::join(
# Present the melody
present_melody(stimuli = melody,
stimuli_type = stimuli_type,
display_modality = display_modality,
page_title = page_title,
page_text = page_text,
page_type = page_type,
answer_meta_data = answer_meta_data,
get_answer = get_answer,
save_answer = save_answer,
button_text = psychTestR::i18n("Record"),
play_button_text = play_button_text,
start_note = start_note,
end_note = end_note,
durations = durations,
state = state,
melody_no = melody_no,
var_name = var_name,
sound = sound,
reactive_stimuli = reactive_stimuli,
rel_to_abs_mel_function = rel_to_abs_mel_function,
max_goes_forced = max_goes_forced,
total_no_melodies = total_no_melodies,
show_progress = show_progress,
sheet_music_start_hidden = sheet_music_start_hidden,
sound_only_first_melody_note = sound_only_first_melody_note,
sheet_music_id = sheet_music_id,
give_first_melody_note = give_first_melody_note,
arrhythmic = arrhythmic,
note_length = note_length,
psychTestRCAT = psychTestRCAT,
volume_meter = volume_meter,
volume_meter_type = volume_meter_type,
singing_trial = singing_trial,
phase = phase,
melody_trial_paradigm = melody_trial_paradigm,
melody_block_paradigm = melody_block_paradigm,
first_note_message = first_note_message,
transposed_message = transposed_message,
play_first_note_button_text = play_first_note_button_text,
reactive_melody_no = reactive_melody_no,
pass_items_through_url_parameter = pass_items_through_url_parameter,
feedback = feedback,
asynchronous_api_mode = asynchronous_api_mode,
mute_midi_playback = mute_midi_playback),
if(is.function(feedback)) feedback(),
# Update and see how to proceed
update_play_melody_loop_and_save(max_goes)
)
) # End psychTestR::while_loop
) # End join
}
present_melody <- function(stimuli,
stimuli_type,
display_modality,
page_title,
page_text,
max_goes_forced = FALSE,
page_type,
answer_meta_data = data.frame(),
get_answer,
save_answer,
button_text,
play_button_text,
start_note = 1L,
end_note,
durations,
state,
melody_no,
var_name,
sound = "piano",
reactive_stimuli = NULL,
rel_to_abs_mel_function = NULL,
hideOnPlay = FALSE,
show_progress = TRUE,
total_no_melodies = 0,
sheet_music_start_hidden = FALSE,
sound_only_first_melody_note = FALSE,
sheet_music_id = 'sheet_music',
give_first_melody_note = FALSE,
arrhythmic = FALSE,
note_length = 0.5,
psychTestRCAT = FALSE,
volume_meter = FALSE,
volume_meter_type = 'default',
singing_trial = TRUE,
melody_block_paradigm = c('standard', 'sing_melody_first', 'learn_phase_visual_display_modality'),
user_rating = FALSE,
happy_with_response = FALSE,
melody_trial_paradigm = c("call_and_response", "simultaneous_recall"),
call_and_response_end = c("manual", "auto"),
first_note_message = psychTestR::i18n("first_note_is"),
transposed_message = psychTestR::i18n("transposed"),
play_first_note_button_text = psychTestR::i18n("play_first_note"),
reactive_melody_no = FALSE,
pass_items_through_url_parameter = FALSE,
feedback = FALSE,
asynchronous_api_mode = FALSE,
mute_midi_playback = FALSE, ...) {
melody_block_paradigm <- match.arg(melody_block_paradigm)
melody_trial_paradigm <- match.arg(melody_trial_paradigm)
call_and_response_end <- match.arg(call_and_response_end)
psychTestR::join(
psychTestR::code_block(function(state, ...) {
# Set some vars for storing in DB
trial_time_started <- Sys.time()
psychTestR::set_global("trial_time_started", trial_time_started, state)
psychTestR::set_global("singing_trial", singing_trial, state)
# Note that, you must set the trial_time_started var in a code_block but not a reactive_page. The latter runs twice and you end up with the wrong time.
}),
psychTestR::reactive_page(function(state, ...) {
# Grab various variables
number_attempts <- psychTestR::get_global("number_attempts", state)
max_goes <- psychTestR::get_global("max_goes", state)
attempts_left <- psychTestR::get_global("attempts_left", state) - 1L
page_label <- paste0(var_name,"_", melody_no, "_attempt_", number_attempts)
transpose_visual_notation <- psychTestR::get_global("transpose_visual_notation", state)
transpose_visual_notation <- if(is.null(transpose_visual_notation)) 0L else transpose_visual_notation
clef <- psychTestR::get_global("clef", state)
clef <- if(is.null(clef)) "auto" else clef
if(length(answer_meta_data) < 1L) {
answer_meta_data <- psychTestR::get_global("answer_meta_data", state)
if(is.null(answer_meta_data)) {
answer_meta_data <- data.frame()
}
}
logging::loginfo("Transpose visual notation play melody loop: %s", transpose_visual_notation)
logging::loginfo("Getting clef: %s", clef)
# MIDI checks
midi_device <- midi_device_check(page_type, state)
# Grab vars
melody_checks <- melody_checks(stimuli, state, stimuli_type, arrhythmic, note_length)
if(psychTestRCAT) {
melody_no <- psychTestR::get_local("item", state) %>%
psychTestRCAT::get_item_number()
}
# Use a display_modality created at test time, if appropriate
display_modality <- if(is.null(melody_checks$display_modality)) display_modality else melody_checks$display_modality
asynchronous_api_mode <- psychTestR::get_global("asynchronous_api_mode", state)
# Get trial paradigm info
trial_paradigm <- paradigm(paradigm_type = melody_trial_paradigm, page_type = page_type, call_and_response_end = call_and_response_end, attempts_left = attempts_left, feedback = is_function_or_true(feedback), asynchronous_api_mode = asynchronous_api_mode)
if(display_modality == "visual") {
notes <- melody_checks$melody
key <- compute_key_on_the_fly(notes)
key_sharps_or_flats <- get_no_sharps_or_flats_from_key(key)
no_accidentals <- count_visible_accidentals(notes, key)
if(key_sharps_or_flats == 0) {
no_sharps <- 0
no_flats <- 0
} else if(key_sharps_or_flats < 0) {
no_sharps <- 0
no_flats <- abs(key_sharps_or_flats)
} else if(key_sharps_or_flats > 0) {
no_sharps <- key_sharps_or_flats
no_flats <- 0
}
additional <- list(
clef = clef,
key_signature = key,
no_flats_key_signature = no_sharps,
no_sharps_key_signature = no_flats,
no_accidentals = no_accidentals
)
} else {
key <- NULL
additional <- list()
}
old_additional <- psychTestR::get_global("additional", state)
if(!is.null(old_additional)) {
if(is.character(old_additional)) {
old_additional <- jsonlite::fromJSON(old_additional)
}
additional <- c(old_additional, additional)
}
psychTestR::set_global("additional", additional, state) # For MIDI
module <- psychTestR::get_local(".module", state)
logging::loginfo("module: %s", module)
db_vars <- if(asynchronous_api_mode) {
list(
stimuli = paste0(melody_checks$melody, collapse = ","), # Note the duplication
stimuli_durations = paste0(melody_checks$durations, collapse = ","),
trial_time_started = psychTestR::get_global("trial_time_started", state),
instrument = if(singing_trial) "Voice" else psychTestR::get_global("inst", state),
attempt = number_attempts,
item_id = if(is.scalar.character(answer_meta_data)) jsonlite::fromJSON(answer_meta_data)$item_id else answer_meta_data$item_id,
display_modality = display_modality,
phase = psychTestR::get_global("phase", state),
rhythmic = !arrhythmic,
session_id = get_promise_value(psychTestR::get_global("session_id", state)),
test_id = psychTestR::get_global("test_id", state),
user_id = psychTestR::get_global("user_id", state),
review_items_id = if(is.scalar.character(answer_meta_data)) jsonlite::fromJSON(answer_meta_data)$review_items_id else answer_meta_data$review_items_id,
new_items_id = if(is.scalar.character(answer_meta_data)) jsonlite::fromJSON(answer_meta_data)$new_items_id else answer_meta_data$new_items_id,
feedback = psychTestR::get_global("async_feedback", state),
feedback_type = psychTestR::get_global("async_feedback_type", state),
melody_block_paradigm = melody_block_paradigm,
trial_paradigm = melody_trial_paradigm,
additional = if(is.scalar.character(additional)) additional else jsonlite::toJSON(additional, auto_unbox = TRUE),
file_type = NA,
noise_filename = NA,
page_label = page_label,
module = module
)
} else NULL
logging::loginfo("Present stimulus")
# Present the stimulus
present_stimuli(stimuli = melody_checks$melody,
stimuli_type = stimuli_type,
display_modality = display_modality,
page_title = page_title,
page_text = page_text,
page_type = page_type,
answer_meta_data = answer_meta_data,
get_answer = get_answer,
save_answer = save_answer,
midi_device = midi_device,
page_label = page_label,
play_button_text = play_button_text,
start_note = melody_checks$start_note,
end_note = melody_checks$end_note,
durations = melody_checks$durations,
user_rating = user_rating,
happy_with_response = happy_with_response,
attempts_left = attempts_left,
sound = sound,
trigger_start_of_stimulus_fun = trial_paradigm$trigger_start_of_stimulus_fun,
trigger_end_of_stimulus_fun = trial_paradigm$trigger_end_of_stimulus_fun,
hideOnPlay = hideOnPlay,
max_goes = max_goes,
max_goes_forced = max_goes_forced,
transpose_visual_notation = transpose_visual_notation,
clef = clef,
melody_no = if(reactive_melody_no) psychTestR::get_global("reactive_melody_no", state) else melody_no,
show_progress = if(reactive_melody_no) FALSE else show_progress,
total_no_melodies = if(pass_items_through_url_parameter) nrow(psychTestR::get_global(var_name, state)) else total_no_melodies,
sheet_music_start_hidden = if(melody_block_paradigm == 'learn_phase_visual_display_modality' && display_modality == "visual") TRUE else sheet_music_start_hidden,
sound_only_first_melody_note = sound_only_first_melody_note,
sheet_music_id = sheet_music_id,
give_first_melody_note = if(melody_block_paradigm == 'learn_phase_visual_display_modality') FALSE else give_first_melody_note,
volume_meter = volume_meter,
volume_meter_type = volume_meter_type,
show_sheet_music_after_record = melody_block_paradigm == 'learn_phase_visual_display_modality' && display_modality == "visual",
show_record_button = melody_block_paradigm == 'learn_phase_visual_display_modality' && display_modality == "visual",
first_note_message = first_note_message,
transposed_message = transposed_message,
play_first_note_button_text = play_first_note_button_text,
reactive_melody_no = reactive_melody_no,
db_vars = db_vars,
lowest_reading_note = psychTestR::get_global("lowest_reading_note", state),
highest_reading_note = psychTestR::get_global("highest_reading_note", state),
feedback = feedback,
asynchronous_api_mode = asynchronous_api_mode,
key = key,
mute_midi_playback = mute_midi_playback)
})
)
}
grab_sampled_melody <- function(item_bank = NULL,
melody_row = NULL,
var_name,
stimuli_type,
state,
melody_no,
arrhythmic,
rel_to_abs_mel_function = NULL,
note_length = 0.5,
get_trial_characteristics_function = NULL,
psychTestRCAT = FALSE,
get_similarity_to_previous_melody = FALSE,
phase = c('test', 'learn', 'review', 'example'),
display_modality = c('auditory', 'visual', 'both'),
reactive_melody_no = FALSE,
learn_test_paradigm = FALSE,
sample_item_bank_via_api = FALSE,
start_from_trial_no = NULL,
pass_items_through_url_parameter = FALSE, ...) {
logging::loginfo("Grab sampled melody")
display_modality <- match.arg(display_modality)
logging::loginfo("Display modality: %s", display_modality)
# Not all trial specifications need a range, instrument. etc but grab it here anyway
bottom_range <- psychTestR::get_global("bottom_range", state)
top_range <- psychTestR::get_global("top_range", state)
range <- psychTestR::get_global("range", state)
inst <- psychTestR::get_global("inst", state)
# Has melody been specified directly, or sampled at test time?
if(is.null(melody_row)) {
logging::loginfo("Sample at test time")
# Assume melodies sampled at test time and stored in global object
if(is.null(get_trial_characteristics_function) || learn_test_paradigm && phase == "test") {
melody_from_state <- grab_melody_from_state(var_name, melody_no, state, psychTestRCAT, rel_to_abs_mel_function, learn_test_paradigm, phase, sample_item_bank_via_api, start_from_trial_no, pass_items_through_url_parameter)
rel_melody <- melody_from_state$rel_melody
melody_row <- melody_from_state$melody_row
abs_melody <- melody_from_state$abs_melody
item_number <- melody_from_state$item_number
melody_no <- if(!is.na(item_number)) item_number else melody_no
} else {
melody_no <- if(reactive_melody_no) psychTestR::get_global("reactive_melody_no", state) else melody_no
trial_characteristics <- psychTestR::get_global(paste0(var_name, "_trial_characteristics"), state)
melody_row <- psychTestR::get_global(var_name, state) %>% dplyr::slice(!! melody_no)
trial_char <- get_trial_characteristics_function(trial_df = trial_characteristics, trial_no = melody_no)
# Not fully abstracted from PBET setup, yet:
abs_melody <- itembankr::str_mel_to_vector(melody_row %>% dplyr::pull(abs_melody))
difficulty <- trial_char$key_difficulty
tranpos_result <- transpose_melody_to_easy_or_hard_key(abs_melody, difficulty, inst, bottom_range, top_range)
abs_melody <- tranpos_result$abs_melody
key <- tranpos_result$key
melody_row$abs_melody <- paste0(abs_melody, collapse = ",")
extra_metadata <- tibble::tibble(key_difficulty = difficulty,
key = key,
display_modality = trial_char$display_modality)
if(learn_test_paradigm) {
if(phase == "example") {
# Then we need to infer the phase
if(any(c("key_difficulty", "key", "display_modality") %in% names(melody_row))) {
extra_metadata <- extra_metadata %>%
dplyr::rename_with(~ paste0("example_test_", .x))
} else {
extra_metadata <- extra_metadata %>%
dplyr::rename_with(~ paste0("example_learn_", .x))
}
} else {
extra_metadata <- extra_metadata %>%
dplyr::rename_with(~ paste0(.x, "_", phase))
}
}
melody_row <- cbind(melody_row, extra_metadata)
psychTestR::set_global("additional",
list(key_difficulty = difficulty,
key = key,
no_notes_in_range = sum(abs_melody %in% bottom_range:top_range) ),
state)
rel_melody <- itembankr::str_mel_to_vector(melody_row %>% dplyr::pull(melody))
rel_to_abs_mel_function <- NULL
display_modality <- trial_char$display_modality
}
} else {
logging::loginfo("Melody has been specified.")
transpose <- transposition_check(melody_row)
rel_melody <- itembankr::str_mel_to_vector(melody_row %>% dplyr::pull(melody))
logging::loginfo("Rel melody: %s", melody_row %>% dplyr::pull(melody))
}
# Does the melody need to be rhythmic or arrhythmic?
durations <- if(arrhythmic) NA else itembankr::str_mel_to_vector(melody_row %>% dplyr::pull(durations))
logging::loginfo("Durations: %s", melody_row %>% dplyr::pull(durations))
melody <- sort_arrhythmic(arrhythmic, rel_melody, durations, note_length)$melody
durations <- sort_arrhythmic(arrhythmic, rel_melody, durations, note_length)$durations
# Does the melody need putting into a certain pitch range...?
abs_melody <- transpose_melody(rel_to_abs_mel_function, rel_melody, abs_melody, melody, bottom_range, top_range, range, transpose)
# Get similarity to previous melody
similarity_to_previous_melody <- get_similarity_to_previous_melody(get_similarity_to_previous_melody, melody_no, state, abs_melody)
# Attach generated absolute melody to meta data
answer_meta_data <- melody_row %>%
dplyr::mutate(similarity_to_previous_melody = similarity_to_previous_melody,
phase = phase,
rhythmic = !arrhythmic,
abs_melody = paste0(abs_melody, collapse = ","), # Thus, we overwrite what was in melody_row (abs_melody may have been transposed)
display_modality = display_modality)
previous_melodies_var_name <- paste0("previous_melodies_", var_name)
prev <- psychTestR::get_global(previous_melodies_var_name, state)
if(is.null(prev)) {
psychTestR::set_global(previous_melodies_var_name, answer_meta_data, state)
} else {
# Make sure they have equal names
joint_column_names <- intersect(names(prev), names(answer_meta_data))
prev <- prev %>% dplyr::select(dplyr::all_of(joint_column_names))
answer_meta_data <- answer_meta_data %>% dplyr::select(dplyr::all_of(joint_column_names))
logging::logdebug("prev %s", prev)
logging::logdebug("answer_meta_data %s", answer_meta_data)
psychTestR::set_global(previous_melodies_var_name, rbind(prev, answer_meta_data), state)
}
# Set the melody to be used
psychTestR::set_global("item_id", melody_row$item_id, state)
psychTestR::set_global("melody", list("melody" = abs_melody, "durations" = durations), state)
psychTestR::set_global("answer_meta_data", jsonlite::toJSON(answer_meta_data), state)
# And other variables
psychTestR::set_global('display_modality', display_modality, state)
psychTestR::set_global("previous_melody", abs_melody, state)
psychTestR::set_global("previous_durations", durations, state)
}
grab_sampled_melody_review <- function(var_name, state, melody_no, arrhythmic, rel_to_abs_mel_function, note_length, pass_items_through_url_parameter) {
logging::loginfo("Grab sampled melody: review")
logging::loginfo("var_name: %s", var_name)
if(!endsWith(var_name, "_review")) {
stop("Review var_names should end with _review")
}
melody_from_state <- grab_melody_from_state(var_name, melody_no, state, psychTestRCAT = FALSE, rel_to_abs_mel_function, pass_items_through_url_parameter = pass_items_through_url_parameter)
arrhythmic <- ! melody_from_state$rhythmic
psychTestR::set_global("answer_meta_data", melody_from_state$melody_row, state)
rel_melody <- melody_from_state$rel_melody
melody_row <- melody_from_state$melody_row
item_id <- melody_from_state$item_id
bottom_range <- psychTestR::get_global("bottom_range", state)
top_range <- psychTestR::get_global("top_range", state)
range <- psychTestR::get_global("range", state)
inst <- psychTestR::get_global("inst", state)
transpose <- transposition_check(melody_row)
durations <- if(arrhythmic) NA else itembankr::str_mel_to_vector(melody_row %>% dplyr::pull(durations))
melody <- sort_arrhythmic(arrhythmic, rel_melody, durations, note_length)$melody
durations <- sort_arrhythmic(arrhythmic, rel_melody, durations, note_length)$durations
abs_melody <- transpose_melody(rel_to_abs_mel_function, rel_melody, abs_melody, melody, bottom_range, top_range, range, transpose)
psychTestR::set_global("melody", list("melody" = abs_melody, "durations" = durations), state)
psychTestR::set_global("item_id", item_id, state)
}
transpose_melody <- function(rel_to_abs_mel_function, rel_melody, abs_melody, melody, bottom_range, top_range, range, transpose) {
logging::loginfo('Transpose melody...')
logging::loginfo("Rel melody: %s", paste0(rel_melody, collapse = ", "))
if(is.null(rel_to_abs_mel_function)) {
if(is.data.frame(abs_melody)) {
abs_melody <- melody %>%
dplyr::pull(abs_melody)
}
} else {
# ...then assume that the melody is in relative format and fit it into a key, based on a rel_to_abs_mel_functionw
abs_melody <- rel_to_abs_mel_function(rel_melody = rel_melody, bottom_range = bottom_range, top_range = top_range, range = range, transpose = transpose)
}
return(abs_melody)
}
transposition_check <- function(melody_row) {
if("transpose" %in% names(melody_row)) {
transpose <- itembankr::str_mel_to_vector(melody_row %>% dplyr::pull(transpose))
}
}
grab_melody_from_state <- function(var_name, melody_no, state, psychTestRCAT = FALSE, rel_to_abs_mel_function = NULL, learn_test_paradigm = FALSE, phase = "test", sample_item_bank_via_api = FALSE, start_from_trial_no = 1L, pass_items_through_url_parameter = FALSE) {
if(psychTestRCAT) {
melody_row <- psychTestR::get_local("item", state)
item_number <- psychTestRCATME::get_item_number(melody_row)
rel_melody <- melody_row %>% dplyr::pull(answer) %>% itembankr::str_mel_to_vector()
if(is.null(rel_to_abs_mel_function)) {
abs_melody <- melody_row %>% dplyr::pull(answer) %>% itembankr::str_mel_to_vector()
}
} else {
if(sample_item_bank_via_api) {
melody_no <- start_from_trial_no + (melody_no-1)
count <- 1
trials <- NULL
while(count < 30 && is.null(trials)) {
trials <- get_promise_value(psychTestR::get_global("sampled_item_bank_from_api", state))
Sys.sleep(1)
logging::loginfo("Try again.. count: %s", count)
count <- count + 1
}
} else {
# Assume melodies sampled at test time and stored in global object
trials <- psychTestR::get_global(var_name, state)
if(pass_items_through_url_parameter) {
melody_no <- 1L # Keep always one and pop off the item each time
psychTestR::set_global(var_name, dplyr::slice(trials, -1), state)
}
}
melody_row <- trials %>% dplyr::slice(!! melody_no)
rel_melody <- melody_row %>% dplyr::pull(melody) %>% itembankr::str_mel_to_vector()
item_id <- melody_row %>% dplyr::pull(item_id)
item_number <- NA
abs_melody <- melody_row %>% dplyr::pull(abs_melody)
}
list(rel_melody = rel_melody,
melody_row = melody_row,
abs_melody = abs_melody,
item_number = item_number,
rhythmic = if(is.null(melody_row$rhythmic)) TRUE else dplyr::pull(melody_row, rhythmic),
item_id = item_id)
}
sort_arrhythmic <- function(arrhythmic, rel_melody, durations, note_length) {
if(arrhythmic) {
durations <- rep(note_length, length(rel_melody) + 1)
}
list(melody = rel_melody, durations = durations)
}
#' Update play melody loop and save
#'
#' @param max_goes
#'
#' @return
#' @export
#'
#' @examples
update_play_melody_loop_and_save <- function(max_goes) {
psychTestR::code_block(function(state, answer, opt, ...) {
logging::loginfo('Update play melody loop and save')
psychTestR::set_global("user_satisfied", answer$user_satisfied, state)
number_attempts <- psychTestR::get_global("number_attempts", state)
attempts_left <- max_goes - number_attempts
psychTestR::set_global("attempts_left", attempts_left, state)
number_attempts <- number_attempts + 1L
psychTestR::set_global("number_attempts", number_attempts, state)
logging::loginfo('Save results to disk...')
psychTestR::save_results_to_disk(complete = FALSE, state, opt)
logging::loginfo('...save results to disk complete.')
})
}
melody_checks <- function(melody, state, stimuli_type = "midi_notes", arrhythmic = FALSE, note_length = 0.5) {
if(is.null(melody)) {
melody <- psychTestR::get_global("melody", state)
}
if(is.data.frame(melody)) {
durations <- melody %>% dplyr::pull(durations) # durations needs to be first
melody <- melody %>% dplyr::pull(abs_melody)
}
if(is.scalar.character(melody)) {
durations <- itembankr::str_mel_to_vector(durations)
melody <- itembankr::str_mel_to_vector(melody)
}
start_note <- 1L
end_note <- "end"
if(is.list(melody)) {
durations <- as.vector(unlist(melody$durations))
melody <- as.vector(unlist(melody$melody))
} else {
durations <- NA
}
if(arrhythmic) {
durations <- rep(note_length, length(melody))
}
list(melody = melody,
start_note = start_note,
end_note = end_note,
durations = durations,
display_modality = psychTestR::get_global("display_modality", state) )
}
midi_device_check <- function(page_type, state, midi_device = " ") {
if(page_type == "record_midi_page") {
midi_device <- psychTestR::get_global("midi_device", state)
if(midi_device == " ") {
shiny::showNotification(psychTestR::i18n("no_midi_device_selected"))
} else {
return(midi_device)
}
}
midi_device
}
get_similarity_to_previous_melody <- function(get_similarity_to_previous_melody, melody_no, state, abs_melody) {
if(get_similarity_to_previous_melody) {
logging::loginfo("Get similarity to previous melody.")
if(melody_no > 1) {
previous_melody <- psychTestR::get_global("previous_melody", state)
if(arrhythmic) {
similarity_to_previous_melody <- ngrukkon_safe(diff(previous_melody), diff(abs_melody))
} else {
previous_durations <- psychTestR::get_global("previous_durations", state)
previous_df <- tibble::tibble(note = previous_melody,
freq = hrep::midi_to_freq(previous_melody), # Doesn't matter, we don't use it here, but req for produce_extra_melodic_features
dur = previous_durations,
onset = cumsum(previous_durations)) %>%
itembankr::produce_extra_melodic_features()
current_df <- tibble::tibble(note = abs_melody,
freq = hrep::midi_to_freq(abs_melody), # Doesn't matter, we don't use it here, but req for produce_extra_melodic_features
dur = durations,
onset = cumsum(durations)) %>%
itembankr::produce_extra_melodic_features()
similarity_to_previous_melody <- opti3_df(previous_df, current_df)$opti3
}
} else {
similarity_to_previous_melody <- NA
}
} else {
similarity_to_previous_melody <- NA
}
similarity_to_previous_melody
}
count_visible_accidentals <- function(notes, key) {
# Convert to scientific notation for easier pitch checking
pitch_classes <- itembankr::midi_to_pitch_class(notes)
# Define expected pitches in the key signature (e.g., G major has F#)
expected_pitches <- get_expected_pitches_for_key(key)
accidental_count <- length(setdiff(pitch_classes, expected_pitches))
return(accidental_count)
}
get_expected_pitches_for_key <- function(key) {
switch(key,
# Major Keys
"C-maj" = c("C", "D", "E", "F", "G", "A", "B"),
"G-maj" = c("G", "A", "B", "C", "D", "E", "F#"),
"D-maj" = c("D", "E", "F#", "G", "A", "B", "C#"),
"A-maj" = c("A", "B", "C#", "D", "E", "F#", "G#"),
"E-maj" = c("E", "F#", "G#", "A", "B", "C#", "D#"),
"B-maj" = c("B", "C#", "D#", "E", "F#", "G#", "A#"),
"F#-maj" = c("F#", "G#", "A#", "B", "C#", "D#", "E#"),
"Gb-maj" = c("Gb", "Ab", "Bb", "Cb", "Db", "Eb", "F"),
"Db-maj" = c("Db", "Eb", "F", "Gb", "Ab", "Bb", "C"),
"C#-maj" = c("C#", "D#", "E#", "F#", "G#", "A#", "B#"), # Enharmonic of Db-maj
"Ab-maj" = c("Ab", "Bb", "C", "Db", "Eb", "F", "G"),
"G#-maj" = c("G#", "A#", "B#", "C#", "D#", "E#", "F##"), # Enharmonic of Ab-maj
"Eb-maj" = c("Eb", "F", "G", "Ab", "Bb", "C", "D"),
"D#-maj" = c("D#", "E#", "F##", "G#", "A#", "B#", "C##"), # Enharmonic of Eb-maj
"Bb-maj" = c("Bb", "C", "D", "Eb", "F", "G", "A"),
"A#-maj" = c("A#", "B#", "C##", "D#", "E#", "F##", "G##"), # Enharmonic of Bb-maj
"F-maj" = c("F", "G", "A", "Bb", "C", "D", "E"),
"E#-maj" = c("E#", "F##", "G##", "A#", "B#", "C##", "D##"), # Enharmonic of F-maj
# Minor Keys
"A-min" = c("A", "B", "C", "D", "E", "F", "G"),
"E-min" = c("E", "F#", "G", "A", "B", "C", "D"),
"B-min" = c("B", "C#", "D", "E", "F#", "G", "A"),
"F#-min" = c("F#", "G#", "A", "B", "C#", "D", "E"),
"Gb-min" = c("Gb", "Ab", "Bbb", "Cb", "Db", "Ebb", "Fb"), # Enharmonic of F#-min
"C#-min" = c("C#", "D#", "E", "F#", "G#", "A", "B"),
"Db-min" = c("Db", "Eb", "Fb", "Gb", "Ab", "Bbb", "Cb"), # Enharmonic of C#-min
"G#-min" = c("G#", "A#", "B", "C#", "D#", "E", "F#"),
"Ab-min" = c("Ab", "Bb", "Cb", "Db", "Eb", "Fb", "Gb"), # Enharmonic of G#-min
"D#-min" = c("D#", "E#", "F#", "G#", "A#", "B", "C#"),
"Eb-min" = c("Eb", "F", "Gb", "Ab", "Bb", "Cb", "Db"), # Enharmonic of D#-min
"A#-min" = c("A#", "B#", "C#", "D#", "E#", "F#", "G#"),
"Bb-min" = c("Bb", "C", "Db", "Eb", "F", "Gb", "Ab"), # Enharmonic of A#-min
"D-min" = c("D", "E", "F", "G", "A", "Bb", "C"),
"G-min" = c("G", "A", "Bb", "C", "D", "Eb", "F"),
"C-min" = c("C", "D", "Eb", "F", "G", "Ab", "Bb"),
"F-min" = c("F", "G", "Ab", "Bb", "C", "Db", "Eb"),
"Bb-min" = c("Bb", "C", "Db", "Eb", "F", "Gb", "Ab"),
"Eb-min" = c("Eb", "F", "Gb", "Ab", "Bb", "Cb", "Db"),
"Ab-min" = c("Ab", "Bb", "Cb", "Db", "Eb", "Fb", "Gb"),
# Default case
stop(paste0("Key ", key, " not recognized"))
)
}
#
#
# # Test 1: C Major scale, no accidentals expected
# notes <- c(60, 62, 64, 65, 67, 69, 71) # MIDI notes for C, D, E, F, G, A, B
# key <- "C-maj"
# accidental_count <- count_visible_accidentals(notes, key)
# print(paste("Test 1 - C Major:", accidental_count)) # Expected output: 0
#
# # Test 2: G Major scale with an F natural
# notes <- c(67, 69, 71, 72, 74, 76, 77) # MIDI notes for G, A, B, C, D, E, F
# key <- "G-maj"
# accidental_count <- count_visible_accidentals(notes, key)
# print(paste("Test 2 - G Major with F natural:", accidental_count)) # Expected output: 1
#
#
# # Test 4: A minor scale with a G#
# notes <- c(69, 71, 72, 74, 76, 77, 80) # MIDI notes for A, B, C, D, E, F, G#
# key <- "A-min"
# accidental_count <- count_visible_accidentals(notes, key)
# print(paste("Test 4 - A Minor with G#:", accidental_count)) # Expected output: 1
#
#
# # Test 5: F Major scale with a B natural
# notes <- c(65, 67, 69, 71, 72, 74, 76) # MIDI notes for F, G, A, B, C, D, E
# key <- "F-maj"
# accidental_count <- count_visible_accidentals(notes, key)
# print(paste("Test 5 - F Major with B natural:", accidental_count)) # Expected output: 1
#
#
# # Test 1: C Major scale in B major
# notes <- c(60, 62, 64, 65, 67, 69, 71) # MIDI notes for C, D, E, F, G, A, B
# key <- "B-maj"
# accidental_count <- count_visible_accidentals(notes, key)
# print(paste("Test 6 - B Major:", accidental_count)) # Expected output: 0
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.