Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions src/media/AudioDisplayComponent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,56 @@ void AudioDisplayComponent::loadMediaFile(const URL& filePath)
32768, // Amount of samples to buffer ahead
&thread, // Thread to use for reading-ahead
audioFileSource->getAudioFormatReader()->sampleRate); // Allows for sample rate correction

// Calculate horizontal center of mass (weighted average of amplitude)
AudioFormatReader* readerPtr = audioFileSource->getAudioFormatReader();
if (readerPtr != nullptr)
{
double sumTimeAmp = 0.0;
double sumAmp = 0.0;

int numChannels = readerPtr->numChannels;
double sampleRate = readerPtr->sampleRate;
int64 lengthInSamples = readerPtr->lengthInSamples;

// Use a large block size for scanning to be fast
int blockSize = 1024;
AudioBuffer<float> buffer(numChannels, blockSize);

int64 position = 0;
while (position < lengthInSamples)
{
int numSamplesToRead =
static_cast<int>(jmin(static_cast<int64>(blockSize), lengthInSamples - position));
readerPtr->read(&buffer, 0, numSamplesToRead, position, true, true);

for (int i = 0; i < numSamplesToRead; ++i)
{
float amp = 0.0f;
for (int ch = 0; ch < numChannels; ++ch)
{
amp += std::abs(buffer.getSample(ch, i));
}
amp /= static_cast<float>(numChannels); // Average across channels

double time = static_cast<double>(position + i) / sampleRate;

sumTimeAmp += time * amp;
sumAmp += amp;
}

position += numSamplesToRead;
}

if (sumAmp > 0.0001)
{
horizontalCenterOfMass = sumTimeAmp / sumAmp;
}
else
{
horizontalCenterOfMass = 0.0;
}
}
}

void AudioDisplayComponent::resetMedia()
Expand All @@ -108,5 +158,8 @@ void AudioDisplayComponent::postLoadActions(const URL& filePath)
{
thumbnailCache.clear();
thumbnail.setSource(inputSource.release());

// Auto-center horizontally about weighted average amplitude
autoZoomToHorizontalMass();
}
}
58 changes: 56 additions & 2 deletions src/media/MediaDisplayComponent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,12 @@ void MediaDisplayComponent::timerCallback()
{
if (isPlaying())
{
// For thumbnail tracks, stop at the end of visible range
if (isThumbnailTrack() && getPlaybackPosition() >= visibleRange.getEnd())
{
stop();
return;
}
updateCursorPosition();
}
else
Expand Down Expand Up @@ -440,8 +446,9 @@ void MediaDisplayComponent::initializeDisplay(const URL& filePath)
if (! isThumbnailTrack())
{
horizontalScrollBar.setVisible(true);
updateVisibleRange({ 0.0, getTotalLengthInSecs() });
}
updateVisibleRange({ 0.0, getTotalLengthInSecs() });

resized(); // Needed to display scrollbar after loading
}

Expand Down Expand Up @@ -813,6 +820,38 @@ void MediaDisplayComponent::updateVisibleRange(Range<double> r)
visibleRangeCallback();
}

void MediaDisplayComponent::autoZoomToHorizontalMass(double previewDuration)
{
if (! isThumbnailTrack())
{
return;
}

double total = getTotalLengthInSecs();
double start = horizontalCenterOfMass - (previewDuration / 2.0);
double end = start + previewDuration;

if (start < 0)
{
start = 0;
end = previewDuration;
}

if (end > total)
{
end = total;
start = total - previewDuration;
}

if (start < 0)
{
start = 0;
}

updateVisibleRange({ start, end });
repaint();
}

void MediaDisplayComponent::horizontalMove(double deltaT)
{
double visibleStart = visibleRange.getStart();
Expand Down Expand Up @@ -951,6 +990,12 @@ void MediaDisplayComponent::deselectTrack()

void MediaDisplayComponent::start()
{
// For thumbnail tracks, start playback from the visible range start
if (isThumbnailTrack())
{
setPlaybackPosition(visibleRange.getStart());
}

startPlaying();

startTimerHz(40);
Expand All @@ -965,7 +1010,16 @@ void MediaDisplayComponent::stop()
stopTimer();

currentPositionCursor.setVisible(false);
setPlaybackPosition(0.0);

// For thumbnail tracks, reset to visible range start instead of 0
if (isThumbnailTrack())
{
setPlaybackPosition(visibleRange.getStart());
}
else
{
setPlaybackPosition(0.0);
}

playStopButton.setMode(playButtonActiveInfo.displayLabel);

Expand Down
3 changes: 3 additions & 0 deletions src/media/MediaDisplayComponent.h
Original file line number Diff line number Diff line change
Expand Up @@ -141,12 +141,15 @@ class MediaDisplayComponent : public Component,
void resetTransport();

virtual void updateVisibleRange(Range<double> r);
void autoZoomToHorizontalMass(double previewDuration = 10.0);

virtual void mouseWheelMove(const MouseEvent&, const MouseWheelDetails& wheel) override;

const int controlSpacing = 1;
const int scrollBarSize = 8;

double horizontalCenterOfMass = 0.0;

// Media (audio or MIDI) content area
Component contentComponent;

Expand Down
31 changes: 31 additions & 0 deletions src/media/MidiDisplayComponent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,33 @@ void MidiDisplayComponent::loadMediaFile(const URL& filePath)

stdDevMidi = std::sqrt(sq_sum / static_cast<float>(numNotes) - mean * mean);

// Calculate horizontal center of mass (median time)
std::vector<double> noteTimes;
for (int eventIdx = 0; eventIdx < allTracks.getNumEvents(); ++eventIdx)
{
const auto midiEvent = allTracks.getEventPointer(eventIdx);
if (midiEvent->message.isNoteOn())
{
noteTimes.push_back(midiEvent->message.getTimeStamp());
}
}

if (! noteTimes.empty())
{
std::sort(noteTimes.begin(), noteTimes.end());
int numTimes = noteTimes.size();
horizontalCenterOfMass = noteTimes[numTimes / 2];
if (numTimes % 2 == 0)
{
horizontalCenterOfMass += noteTimes[numTimes / 2 - 1];
horizontalCenterOfMass /= 2.0;
}
}
else
{
horizontalCenterOfMass = 0.0;
}

synthAudioSource.useSequence(allTracks);
transportSource.setSource(&synthAudioSource);
}
Expand All @@ -180,6 +207,9 @@ void MidiDisplayComponent::postLoadActions(const URL& /*filePath*/)
{
// Auto-center pianoRoll vertically about median note
pianoRoll.autoCenterViewBox(medianMidi, stdDevMidi);

// Auto-center horizontally about median time
autoZoomToHorizontalMass();
}

float MidiDisplayComponent::getVerticalControlsWidth()
Expand Down Expand Up @@ -207,6 +237,7 @@ void MidiDisplayComponent::updateVisibleRange(Range<double> newRange)
MediaDisplayComponent::updateVisibleRange(newRange);

pianoRoll.updateVisibleMediaRange(newRange);
pianoRoll.repaint();
}

void MidiDisplayComponent::verticalMove(double deltaY)
Expand Down
24 changes: 9 additions & 15 deletions src/media/pianoroll/PianoRollComponent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,11 @@ void PianoRollComponent::resized()

double keyHeight = getKeyHeight();

// Set size of keyboard and note grid according to key height
// Set size of keyboard according to key height
keyboard.setSize(getKeyboardWidth(), static_cast<int>(128.0 * keyHeight));
noteGrid.setSize(getPianoRollContainerWidth(), static_cast<int>(128.0 * keyHeight));

double pixelsPerSecond = 0.0;
double totalLength = totalMediaLength;

if (visibleMediaRange.getLength() > 0)
{
Expand All @@ -72,6 +72,12 @@ void PianoRollComponent::resized()
// Update horizontal pianoroll resolution
setResolution(pixelsPerSecond);

// Update noteGrid resolution and size to match the zoom level
noteGrid.setResolution(pixelsPerSecond);
int noteGridWidth = static_cast<int>(totalLength * pixelsPerSecond);
noteGridWidth = jmax(noteGridWidth, getPianoRollContainerWidth());
noteGrid.setSize(noteGridWidth, static_cast<int>(128.0 * keyHeight));

int currYPosition = static_cast<int>(visibleKeyRange.getStart() * getKeyHeight());
int currXPosition = static_cast<int>(visibleMediaRange.getStart() * getResolution());

Expand Down Expand Up @@ -112,19 +118,7 @@ double PianoRollComponent::keysVisibleToZoom(float numKeysVisible)
/ static_cast<double>(maxKeysVisible - minKeysVisible);
}

void PianoRollComponent::resizeNoteGrid(double lengthInSecs)
{
noteGrid.setLength(lengthInSecs);

if (lengthInSecs > 0)
{
noteGrid.setResolution(static_cast<double>(getPianoRollContainerWidth()) / lengthInSecs);
}
else
{
noteGrid.setResolution(0);
}
}
// resizeNoteGrid is now inline in PianoRollComponent.hpp

void PianoRollComponent::updateVisibleMediaRange(Range<double> newRange)
{
Expand Down
7 changes: 6 additions & 1 deletion src/media/pianoroll/PianoRollComponent.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,11 @@ class PianoRollComponent : public Component, public ChangeBroadcaster, private S
//Range<double> getVisibleMediaRange() { return visibleMediaRange; }
Range<double> getVisibleKeyRange() { return visibleKeyRange; }

void resizeNoteGrid(double lengthInSecs);
void resizeNoteGrid(double lengthInSecs)
{
totalMediaLength = lengthInSecs;
noteGrid.setLength(lengthInSecs);
}

void insertNote(MidiNote n) { noteGrid.insertNote(n); }
void resetNotes() { noteGrid.resetNotes(); }
Expand Down Expand Up @@ -91,6 +95,7 @@ class PianoRollComponent : public Component, public ChangeBroadcaster, private S
Viewport noteGridContainer;

Range<double> visibleMediaRange;
double totalMediaLength = 0.0;

int minKeysVisible = 5;
int maxKeysVisible = 16;
Expand Down
Loading