diff --git a/include/Clip.h b/include/Clip.h index ea54162f..f30844b2 100644 --- a/include/Clip.h +++ b/include/Clip.h @@ -155,6 +155,7 @@ namespace openshot { ScaleType scale; ///< The scale determines how a clip should be resized to fit it's parent AnchorType anchor; ///< The anchor determines what parent a clip should snap to FrameDisplayType display; ///< The format to display the frame number (if any) + VolumeMixType mixing; ///< What strategy should be followed when mixing audio with other clips /// Default Constructor Clip(); diff --git a/include/Enums.h b/include/Enums.h index 8e994a1b..fb91f1fa 100644 --- a/include/Enums.h +++ b/include/Enums.h @@ -69,5 +69,13 @@ namespace openshot FRAME_DISPLAY_TIMELINE, ///< Display the timeline's frame number FRAME_DISPLAY_BOTH ///< Display both the clip's and timeline's frame number }; + + /// This enumeration determines the strategy when mixing audio with other clips. + enum VolumeMixType + { + VOLUME_MIX_NONE, ///< Do not apply any volume mixing adjustments. Just add the samples together. + VOLUME_MIX_AVERAGE, ///< Evenly divide the overlapping clips volume keyframes, so that the sum does not exceed 100% + VOLUME_MIX_REDUCE ///< Reduce volume by about %25, and then mix (louder, but could cause pops if the sum exceeds 100%) + }; } #endif diff --git a/src/Clip.cpp b/src/Clip.cpp index 007b0a59..913fd71f 100644 --- a/src/Clip.cpp +++ b/src/Clip.cpp @@ -41,6 +41,7 @@ void Clip::init_settings() scale = SCALE_FIT; anchor = ANCHOR_CANVAS; display = FRAME_DISPLAY_NONE; + mixing = VOLUME_MIX_NONE; waveform = false; previous_properties = ""; @@ -694,6 +695,7 @@ string Clip::PropertiesJSON(int64_t requested_frame) { root["gravity"] = add_property_json("Gravity", gravity, "int", "", NULL, 0, 8, false, requested_frame); root["scale"] = add_property_json("Scale", scale, "int", "", NULL, 0, 3, false, requested_frame); root["display"] = add_property_json("Frame Number", display, "int", "", NULL, 0, 3, false, requested_frame); + root["mixing"] = add_property_json("Volume Mixing", mixing, "int", "", NULL, 0, 2, false, requested_frame); root["waveform"] = add_property_json("Waveform", waveform, "int", "", NULL, 0, 1, false, requested_frame); // Add gravity choices (dropdown style) @@ -719,6 +721,11 @@ string Clip::PropertiesJSON(int64_t requested_frame) { root["display"]["choices"].append(add_property_choice_json("Timeline", FRAME_DISPLAY_TIMELINE, display)); root["display"]["choices"].append(add_property_choice_json("Both", FRAME_DISPLAY_BOTH, display)); + // Add volume mixing choices (dropdown style) + root["mixing"]["choices"].append(add_property_choice_json("None", VOLUME_MIX_NONE, mixing)); + root["mixing"]["choices"].append(add_property_choice_json("Average", VOLUME_MIX_AVERAGE, mixing)); + root["mixing"]["choices"].append(add_property_choice_json("Reduce", VOLUME_MIX_REDUCE, mixing)); + // Add waveform choices (dropdown style) root["waveform"]["choices"].append(add_property_choice_json("Yes", true, waveform)); root["waveform"]["choices"].append(add_property_choice_json("No", false, waveform)); @@ -758,6 +765,7 @@ Json::Value Clip::JsonValue() { root["scale"] = scale; root["anchor"] = anchor; root["display"] = display; + root["mixing"] = mixing; root["waveform"] = waveform; root["scale_x"] = scale_x.JsonValue(); root["scale_y"] = scale_y.JsonValue(); @@ -844,6 +852,8 @@ void Clip::SetJsonValue(Json::Value root) { anchor = (AnchorType) root["anchor"].asInt(); if (!root["display"].isNull()) display = (FrameDisplayType) root["display"].asInt(); + if (!root["mixing"].isNull()) + mixing = (VolumeMixType) root["mixing"].asInt(); if (!root["waveform"].isNull()) waveform = root["waveform"].asBool(); if (!root["scale_x"].isNull()) diff --git a/src/Timeline.cpp b/src/Timeline.cpp index 80e92503..1b4f4750 100644 --- a/src/Timeline.cpp +++ b/src/Timeline.cpp @@ -294,11 +294,24 @@ void Timeline::add_layer(std::shared_ptr new_frame, Clip* source_clip, in if (source_frame->GetAudioChannelsCount() == info.channels && source_clip->has_audio.GetInt(clip_frame_number) != 0) for (int channel = 0; channel < source_frame->GetAudioChannelsCount(); channel++) { - float previous_volume = source_clip->volume.GetValue(clip_frame_number - 1) / fmaxf(max_volume, 1.0); // previous frame's percentage of volume (0 to 1) - float volume = source_clip->volume.GetValue(clip_frame_number) / fmaxf(max_volume, 1.0); // percentage of volume (0 to 1) + // Get volume from previous frame and this frame + float previous_volume = source_clip->volume.GetValue(clip_frame_number - 1); + float volume = source_clip->volume.GetValue(clip_frame_number); int channel_filter = source_clip->channel_filter.GetInt(clip_frame_number); // optional channel to filter (if not -1) int channel_mapping = source_clip->channel_mapping.GetInt(clip_frame_number); // optional channel to map this channel to (if not -1) + // Apply volume mixing strategy + if (source_clip->mixing == VOLUME_MIX_AVERAGE && max_volume > 1.0) { + // Don't allow this clip to exceed 100% (divide volume equally between all overlapping clips with volume + previous_volume = previous_volume / max_volume; + volume = volume / max_volume; + } + else if (source_clip->mixing == VOLUME_MIX_REDUCE && max_volume > 1.0) { + // Reduce clip volume by a bit, hoping it will prevent exceeding 100% (but it is very possible it will) + previous_volume = previous_volume * 0.77; + volume = volume * 0.77; + } + // If channel filter enabled, check for correct channel (and skip non-matching channels) if (channel_filter != -1 && channel_filter != channel) continue; // skip to next channel @@ -760,14 +773,18 @@ std::shared_ptr Timeline::GetFrame(int64_t requested_frame) long nearby_clip_start_frame = (nearby_clip->Start() * info.fps.ToDouble()) + 1; long nearby_clip_frame_number = frame_number - nearby_clip_start_position + nearby_clip_start_frame; + // Determine if top clip if (clip->Id() != nearby_clip->Id() && clip->Layer() == nearby_clip->Layer() && nearby_clip_start_position <= frame_number && nearby_clip_end_position >= frame_number && nearby_clip_start_position > clip_start_position && is_top_clip == true) { is_top_clip = false; } - if (nearby_clip_start_position <= frame_number && nearby_clip_end_position >= frame_number) { - max_volume += nearby_clip->volume.GetValue(nearby_clip_frame_number); + // Determine max volume of overlapping clips + if (nearby_clip->Reader() && nearby_clip->Reader()->info.has_audio && + nearby_clip->has_audio.GetInt(nearby_clip_frame_number) != 0 && + nearby_clip_start_position <= frame_number && nearby_clip_end_position >= frame_number) { + max_volume += nearby_clip->volume.GetValue(nearby_clip_frame_number); } }