diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index 03bae6e4abfd7e18765d9c5e6de66a6642d89505..443fa518224679a0220cb97f46ddafe7238c2056 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -16,8 +16,11 @@ Test2AudioProcessorEditor::Test2AudioProcessorEditor(Test2AudioProcessor &p) // Make sure that before the constructor has finished, you've set the // editor's size to whatever you need it to be. addAndMakeVisible(positionLabel); + // add a label that will display the current timecode and status.. + addAndMakeVisible(timecodeDisplayLabel); + timecodeDisplayLabel.setFont(juce::Font(juce::Font::getDefaultMonospacedFontName(), 15.0f, juce::Font::plain)); + positionLabel.setText("Text input:", juce::dontSendNotification); - // positionLabel.attachToComponent(&inputText, true); positionLabel.setColour(juce::Label::textColourId, juce::Colours::orange); positionLabel.setJustificationType(juce::Justification::right); startTimerHz(60); @@ -40,7 +43,7 @@ void Test2AudioProcessorEditor::paint(juce::Graphics &g) g.setColour(juce::Colours::white); g.setFont(15.0f); - g.drawFittedText("1) Position : " + std::to_string(processor.position), getLocalBounds(), juce::Justification::centred, 1); + g.drawFittedText("3) Position : " + std::to_string(processor.position), getLocalBounds(), juce::Justification::centred, 1); // if (processor.m_flogger) // processor.m_flogger->logMessage("paint called"); } @@ -52,7 +55,7 @@ void Test2AudioProcessorEditor::resized() auto bounds = getLocalBounds(); positionLabel.setBounds(bounds.removeFromBottom(30).withSizeKeepingCentre(50, 24)); - + timecodeDisplayLabel.setBounds(bounds.removeFromTop(26)); if (processor.m_flogger) processor.m_flogger->logMessage(bounds.toString()); } diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index 271c020321e93922cf90736a005bd889bfffa553..c8ee2268bccefdc8d9475dafc152beac8d218756 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -29,7 +29,7 @@ private: // access the processor object that created it. Test2AudioProcessor &processor; // is this the way - juce::Label positionLabel; // = juce::Label("Position", "Position : 0"); + juce::Label positionLabel, timecodeDisplayLabel; // = juce::Label("Position", "Position : 0"); // or something like //juce::Label positionLabel = {"Position"}; @@ -40,7 +40,55 @@ private: void timerCallback() override { positionLabel.setText("Pos : " + std::to_string(processor.position), juce::dontSendNotification); + updateTimecodeDisplay(processor.lastPosInfo); repaint(); } + // Updates the text in our position label. + void updateTimecodeDisplay(juce::AudioPlayHead::CurrentPositionInfo pos) + { + juce::MemoryOutputStream displayText; + + displayText << "[" << juce::SystemStats::getJUCEVersion() << "] " + << juce::String(pos.bpm, 2) << " bpm, " + << pos.timeSigNumerator << '/' << pos.timeSigDenominator + << " - " << timeToTimecodeString(pos.timeInSeconds) + << " - " << quarterNotePositionToBarsBeatsString(pos.ppqPosition, pos.timeSigNumerator, pos.timeSigDenominator); + + if (pos.isRecording) + displayText << " (recording)"; + else if (pos.isPlaying) + displayText << " (playing)"; + + timecodeDisplayLabel.setText(displayText.toString(), juce::dontSendNotification); + } + + //============================================================================== + // quick-and-dirty function to format a timecode string + static juce::String timeToTimecodeString(double seconds) + { + auto millisecs = juce::roundToInt(seconds * 1000.0); + auto absMillisecs = std::abs(millisecs); + + return juce::String::formatted("%02d:%02d:%02d.%03d", + millisecs / 3600000, + (absMillisecs / 60000) % 60, + (absMillisecs / 1000) % 60, + absMillisecs % 1000); + } + // quick-and-dirty function to format a bars/beats string + static juce::String quarterNotePositionToBarsBeatsString(double quarterNotes, int numerator, int denominator) + { + if (numerator == 0 || denominator == 0) + return "1|1|000"; + + auto quarterNotesPerBar = (numerator * 4 / denominator); + auto beats = (fmod(quarterNotes, quarterNotesPerBar) / quarterNotesPerBar) * numerator; + + auto bar = ((int)quarterNotes) / quarterNotesPerBar + 1; + auto beat = ((int)beats) + 1; + auto ticks = ((int)(fmod(beats, 1.0) * 960.0 + 0.5)); + + return juce::String::formatted("%d|%d|%03d", bar, beat, ticks); + } JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(Test2AudioProcessorEditor) }; diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 9612e862d6f2517bc0e6444346b9f8a9ef87a567..0dabedddebde8f8aaf9f8ea81e3f48698789fd55 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -26,6 +26,12 @@ Test2AudioProcessor::Test2AudioProcessor() position = 0; + addParameter(gain = new juce::AudioParameterFloat("no_gain", // parameter ID + "NoGain", // parameter name + 0.0f, // minimum value + 1.0f, // maximum value + 0.5f)); // default value + if (m_flogger) m_flogger->logMessage("PluginProcessor"); } @@ -204,12 +210,15 @@ void Test2AudioProcessor::getStateInformation(juce::MemoryBlock &destData) // You should use this method to store your parameters in the memory block. // You could do that either as raw data, or use the XML or ValueTree classes // as intermediaries to make it easy to save and load complex data. + juce::MemoryOutputStream(destData, true).writeFloat(*gain); } void Test2AudioProcessor::setStateInformation(const void *data, int sizeInBytes) { // You should use this method to restore your parameters from this memory block, // whose contents will have been created by the getStateInformation() call. + + *gain = juce::MemoryInputStream(data, static_cast<size_t>(sizeInBytes), false).readFloat(); } //============================================================================== diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index 06e1bb4d02e8c30f46c7cdf05883e1aeeef97e97..6c52255e22c5df79a528b6ff079287781457b1ee 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -55,8 +55,30 @@ public: std::unique_ptr<juce::FileLogger> m_flogger; int position; + // this keeps a copy of the last set of time info that was acquired during an audio + // callback - the UI component will read this and display it. + juce::AudioPlayHead::CurrentPositionInfo lastPosInfo; + + void updateCurrentTimeInfoFromHost() + { + if (auto *ph = getPlayHead()) + { + juce::AudioPlayHead::CurrentPositionInfo newTime; + + if (ph->getCurrentPosition(newTime)) + { + lastPosInfo = newTime; // Successfully got the current time from the host.. + return; + } + } + + // If the host fails to provide the current time, we'll just reset our copy to a default.. + lastPosInfo.resetToDefault(); + } private: + //============================================================================== + juce::AudioParameterFloat *gain; //============================================================================== JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(Test2AudioProcessor) };