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);
}
}