Skip to content

Commit

Permalink
Release 0.15.0.0-alpha1
Browse files Browse the repository at this point in the history
  • Loading branch information
stuerp committed Jun 26, 2024
1 parent 4fabf74 commit 857bd77
Show file tree
Hide file tree
Showing 12 changed files with 270 additions and 88 deletions.
4 changes: 2 additions & 2 deletions Preferences.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

/** $VER: Preferences.cpp (2024.06.23) P. Stuer **/
/** $VER: Preferences.cpp (2024.06.26) P. Stuer **/

#include "pch.h"

Expand Down Expand Up @@ -165,7 +165,7 @@ class Preferences : public CDialogImpl<Preferences>, public preferences_page_ins

DirectoryPath.truncate_filename();

if (::uBrowseForFolder(m_hWnd, "Locate the EdgeView user data folder...", DirectoryPath))
if (::uBrowseForFolder(m_hWnd, "Locate the WebView user data folder...", DirectoryPath))
{
SetDlgItemTextW(IDC_USER_DATA_FOLDER_PATH, ::UTF8ToWide(DirectoryPath.c_str()).c_str());

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,10 @@ To create the component first build the x86 configuration and next the x64 confi

## Change Log

v0.1.5.0, 2024-06-xx
v0.1.5.0, 2024-06-26-alpha1

* New: Each instance of the component can have a name to easier distinguish between them.
* New: The location of the EdgeView user data folder can be specified in the Preferences dialog.
* New: The location of the WebView user data folder can be specified in the Preferences dialog.
* Note: The existing folder will not be moved or deleted. The new location will only be used after restarting foobar2000.
* New: Made the sample chunks from the foobar2000 visualisation stream available to Javascript.
* Fixed: A last minute change (never a good thing) broke the support for multiple instances of the Preferences dialog box.
Expand Down
56 changes: 14 additions & 42 deletions Rendering.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
/// </summary>
void UIElement::StartTimer() noexcept
{
::SetTimer(m_hWnd, (UINT_PTR) this, 1000 / (DWORD) 50, TimerCallback);
::SetTimer(m_hWnd, (UINT_PTR) this, 1000 / (DWORD) 50, (TIMERPROC) TimerCallback);
}

/// <summary>
Expand Down Expand Up @@ -51,25 +51,26 @@ void UIElement::OnTimer() noexcept

audio_chunk_impl Chunk;

const double WindowSize = 0.05; // in seconds
const double WindowSize = 0.10; // in seconds
const double Offset = PlaybackTime - (WindowSize / 2.);

if (!_VisualisationStream->get_chunk_absolute(Chunk, Offset, WindowSize))
return;

const audio_sample * Samples = Chunk.get_data();

size_t SampleCount = (uint32_t) Chunk.get_sample_count();
uint32_t ChannelCount = Chunk.get_channel_count();
size_t SampleCount = Chunk.get_sample_count();
uint32_t SampleRate = Chunk.get_sample_rate();
uint32_t ChannelCount = Chunk.get_channel_count();
uint32_t ChannelConfig = Chunk.get_channel_config();

HRESULT hr = PostChunk(Samples, SampleCount, ChannelCount, SampleRate);
HRESULT hr = PostChunk(Samples, SampleCount, SampleRate, ChannelCount, ChannelConfig);

if (!SUCCEEDED(hr))
return;

{
hr = _WebView->ExecuteScript(::FormatText(L"OnTimer(%d, %d, %d)", SampleCount, ChannelCount, SampleRate).c_str(), nullptr); // Silently continue
hr = _WebView->ExecuteScript(::FormatText(L"OnTimer(%d, %d, %d, %08X)", SampleCount, SampleRate, ChannelCount, ChannelConfig).c_str(), nullptr); // Silently continue

if (!SUCCEEDED(hr))
{
Expand All @@ -83,44 +84,15 @@ void UIElement::OnTimer() noexcept
/// <summary>
/// Posts a chunk to the script via a shared buffer.
/// </summary>
HRESULT UIElement::PostChunk(const audio_sample * samples, size_t sampleCount, uint32_t channelCount, uint32_t sampleRate) noexcept
HRESULT UIElement::PostChunk(const audio_sample * samples, size_t sampleCount, uint32_t sampleRate, uint32_t channelCount, uint32_t channelConfig) noexcept
{
const UINT64 Size = sizeof(audio_sample) * sampleCount;

if (_SharedBuffer == nullptr)
{
wil::com_ptr<ICoreWebView2Environment12> Environment;

HRESULT hr = _Environment->QueryInterface(IID_PPV_ARGS(&Environment));

if (!SUCCEEDED(hr))
return hr;

hr = Environment->CreateSharedBuffer(Size, &_SharedBuffer);

if (!SUCCEEDED(hr))
return hr;

hr = _SharedBuffer->get_Buffer(&_Buffer);

if (!SUCCEEDED(hr))
return hr;

_WebView17 = _WebView.try_query<ICoreWebView2_17>();

if (_WebView17 == nullptr)
return E_NOINTERFACE;

std::wstring AdditionalDataAsJson = ::FormatText(L"{\"SampleCount\":%d,\"ChannelCount\":%d,\"SampleRate\":%d}", (int) sampleCount, (int) channelCount, (int) sampleRate);

hr = _WebView17->PostSharedBufferToScript(_SharedBuffer.get(), COREWEBVIEW2_SHARED_BUFFER_ACCESS_READ_WRITE, AdditionalDataAsJson.c_str());

if (!SUCCEEDED(hr))
return hr;
}
HRESULT hr = _SharedBuffer.Ensure(_Environment, _WebView, sampleCount, sampleRate, channelCount, channelConfig);

if (_Buffer != nullptr)
::memcpy(_Buffer, samples, Size);
if (SUCCEEDED(hr))
if (audio_sample_size == 64)
_SharedBuffer.Copy((const BYTE *) samples, sizeof(audio_sample) * sampleCount);
else
_SharedBuffer.Convert((const float *) samples, sampleCount);

return S_OK;
}
8 changes: 4 additions & 4 deletions Resources.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

/** $VER: Resources.h (2024.06.23) P. Stuer **/
/** $VER: Resources.h (2024.06.26) P. Stuer **/

#pragma once

Expand All @@ -19,7 +19,7 @@
/** Component specific **/

#define STR_COMPONENT_NAME "Text Visualizer"
#define STR_COMPONENT_VERSION TOSTRING(NUM_FILE_MAJOR) "." TOSTRING(NUM_FILE_MINOR) "." TOSTRING(NUM_FILE_PATCH) "." TOSTRING(NUM_FILE_PRERELEASE)
#define STR_COMPONENT_VERSION TOSTRING(NUM_FILE_MAJOR) "." TOSTRING(NUM_FILE_MINOR) "." TOSTRING(NUM_FILE_PATCH) "." TOSTRING(NUM_FILE_PRERELEASE) "-alpha1"
#define STR_COMPONENT_BASENAME "foo_vis_text"
#define STR_COMPONENT_FILENAME STR_COMPONENT_BASENAME ".dll"
#define STR_COMPONENT_COMPANY_NAME ""
Expand All @@ -36,11 +36,11 @@
#define STR_COPYRIGHT TEXT(STR_COMPONENT_COPYRIGHT)

#define STR_FILE_NAME TEXT(STR_COMPONENT_FILENAME)
#define STR_FILE_VERSION TOSTRING(NUM_FILE_MAJOR) TEXT(".") TOSTRING(NUM_FILE_MINOR) TEXT(".") TOSTRING(NUM_FILE_PATCH) TEXT(".") TOSTRING(NUM_FILE_PRERELEASE)
#define STR_FILE_VERSION TOSTRING(NUM_FILE_MAJOR) TEXT(".") TOSTRING(NUM_FILE_MINOR) TEXT(".") TOSTRING(NUM_FILE_PATCH) TEXT(".") TOSTRING(NUM_FILE_PRERELEASE) "-alpha1"
#define STR_FILE_DESCRIPTION TEXT(STR_COMPONENT_DESCRIPTION)

#define STR_PRODUCT_NAME STR_INTERNAL_NAME
#define STR_PRODUCT_VERSION TOSTRING(NUM_PRODUCT_MAJOR) TEXT(".") TOSTRING(NUM_PRODUCT_MINOR) TEXT(".") TOSTRING(NUM_PRODUCT_PATCH) TEXT(".") TOSTRING(NUM_PRODUCT_PRERELEASE)
#define STR_PRODUCT_VERSION TOSTRING(NUM_PRODUCT_MAJOR) TEXT(".") TOSTRING(NUM_PRODUCT_MINOR) TEXT(".") TOSTRING(NUM_PRODUCT_PATCH) TEXT(".") TOSTRING(NUM_PRODUCT_PRERELEASE) "-alpha1"

#define STR_ABOUT_NAME STR_INTERNAL_NAME
#define STR_ABOUT_WEB TEXT("https://github.com/stuerp/") STR_COMPONENT_BASENAME
Expand Down
116 changes: 116 additions & 0 deletions SharedBuffer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@

/** $VER: SharedBuffer.cpp (2024.06.26) P. Stuer **/

#include "pch.h"

#include "SharedBuffer.h"

#include "Encoding.h"

#pragma hdrstop

using namespace Microsoft::WRL;

/// <summary>
/// Ensures that a buffer of the correct size is posted to the WebView.
/// </summary>
HRESULT SharedBuffer::Ensure(wil::com_ptr<ICoreWebView2Environment> & environment, wil::com_ptr<ICoreWebView2> & webView, size_t sampleCount, uint32_t sampleRate, uint32_t channelCount, uint32_t channelConfig) noexcept
{
if ((_Buffer != nullptr) && (_SampleCount == sampleCount) && (_SampleRate == sampleRate) && (_ChannelCount == channelCount) && (_ChannelConfig == channelConfig))
return S_OK;

Release();

HRESULT hr = environment->QueryInterface(IID_PPV_ARGS(&_Environment12));

if (!SUCCEEDED(hr))
return hr;

_WebView17 = webView.try_query<ICoreWebView2_17>();

if (_WebView17 == nullptr)
return E_NOINTERFACE;

_Size = sizeof(double) * sampleCount * channelCount; // Don't use audio_sample.

hr = _Environment12->CreateSharedBuffer(_Size, &_SharedBuffer);

if (!SUCCEEDED(hr))
return hr;

hr = _SharedBuffer->get_Buffer(&_Buffer);

if (!SUCCEEDED(hr))
return hr;

std::wstring AdditionalDataAsJson = ::FormatText(L"{\"SampleCount\":%d,\"SampleRate\":%d,\"ChannelCount\":%d,\"ChannelConfig\":%d}", (int) sampleCount, (int) sampleRate, (int) channelCount, (int) channelConfig);

hr = _WebView17->PostSharedBufferToScript(_SharedBuffer.get(), COREWEBVIEW2_SHARED_BUFFER_ACCESS_READ_WRITE, AdditionalDataAsJson.c_str());

if (!SUCCEEDED(hr))
return hr;

_SampleCount = sampleCount;
_SampleRate = sampleRate;
_ChannelCount = channelCount;
_ChannelConfig = channelConfig;

return S_OK;
}

/// <summary>
/// Releases the resources of this instance.
/// </summary>
void SharedBuffer::Release() noexcept
{
_SampleRate = 0;
_ChannelCount = 0;
_SampleCount = 0;

_Buffer = nullptr;
_SharedBuffer = nullptr;
_WebView17 = nullptr;
_Environment12 = nullptr;
}

/// <summary>
/// Copies data to the buffer.
/// </summary>
void SharedBuffer::Copy(const BYTE * data, size_t size) noexcept
{
if (_Buffer == nullptr)
return;

if (_Size < size)
size = (size_t) _Size;

::memcpy(_Buffer, data, size);
}

/// <summary>
/// Fills the buffer with samples converted from 32-bit to 64-floats.
/// </summary>
void SharedBuffer::Convert(const float * sampleData, size_t sampleCount) noexcept
{
if (_Buffer == nullptr)
return;

size_t Size = sizeof(double) * sampleCount * _ChannelCount;

if (_Size < Size)
sampleCount = _SampleCount;

const float * p = sampleData;
double * q = (double *) _Buffer;

for (size_t i = 0; i < sampleCount * _ChannelCount; ++i)
*q++ = (double) *p++;
}

/// <summary>
/// Deletes this instance.
/// </summary>
SharedBuffer::~SharedBuffer()
{
Release();
}
40 changes: 40 additions & 0 deletions SharedBuffer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@

/** $VER: SharedBuffer.h (2024.06.26) P. Stuer - Implements a buffer shared by the component and WebView. **/

#pragma once

#include "framework.h"

#include <wrl.h>
#include <wil/com.h>

#include <WebView2.h>

/// <summary>
/// Implements a shared buffer.
/// </summary>
class SharedBuffer
{
public:
SharedBuffer() : _SampleCount(), _SampleRate(), _ChannelCount(), _ChannelConfig() { }

virtual ~SharedBuffer();

HRESULT Ensure(wil::com_ptr<ICoreWebView2Environment> & environment, wil::com_ptr<ICoreWebView2> & webView, size_t sampleCount, uint32_t sampleRate, uint32_t channelCount, uint32_t channelConfig) noexcept;
void Release() noexcept;

void Copy(const BYTE * data, size_t size) noexcept;
void Convert(const float * sampleData, size_t sampleCount) noexcept;

private:
size_t _SampleCount;
uint32_t _SampleRate;
uint32_t _ChannelCount;
uint32_t _ChannelConfig;

wil::com_ptr<ICoreWebView2Environment12> _Environment12;
wil::com_ptr<ICoreWebView2_17> _WebView17;
wil::com_ptr<ICoreWebView2SharedBuffer> _SharedBuffer;
UINT64 _Size;
BYTE * _Buffer;
};
54 changes: 49 additions & 5 deletions Template.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
padding: 0;
font: 90% Calibri, Sans-serif;
line-height: 1.2;
color: #555753;
color: #555753;
background-color: #e0e0e0;
}
div
Expand Down Expand Up @@ -84,8 +84,21 @@
Time: <span id="Time">?</span>s<br/>
Volume: <span id="Volume">?</span>dBFS<br/>
</p>
<span id="Timer"></span><br/>
<span id="SampleCount"></span> samples, <span id="SampleRate"></span>Hz, <span id="ChannelCount"></span> channels (<span id="ChannelConfig"></span>)<br/>
</div>
<script>
window.onload = function ()
{
window.chrome.webview.addEventListener("sharedbufferreceived", e =>
{
OnSharedBufferReceived(e);
});
}

let SharedBuffer;
let Samples;

// Called when playback is being initialized.
function OnPlaybackStarting(command, paused)
{
Expand All @@ -108,6 +121,12 @@
document.getElementById("StopReason").textContent = reason; // "User" / "EOF" / "Starting another" / "Shutting down"

document.getElementById("Info").style.display = 'none';

if (SharedBuffer)
{
window.chrome.webview.releaseBuffer(SharedBuffer);
SharedBuffer = null;
}
}

// Called when the user seeks to a specific time.
Expand Down Expand Up @@ -197,14 +216,39 @@
document.getElementById("MIDI").textContent = chrome.webview.hostObjects.sync.foo_vis_text.GetFormattedText("['MIDI: '$info(midi_player)][, $info(midi_active_voices) voices '(peak ' $info(midi_peak_voices)')'][, extra percussion channel $info(midi_extra_percussion_channel)]");
}

let Chunk;
function OnSharedBufferReceived(e)
{
if (SharedBuffer)
{
window.chrome.webview.releaseBuffer(SharedBuffer);
SharedBuffer = null;
}

if (!e.additionalData)
return;

SharedBuffer = e.getBuffer(); // as an ArrayBuffer

Samples = new Float64Array(SharedBuffer);

function SharedBufferReceived(e)
document.getElementById("SampleCount").textContent = e.additionalData.SampleCount;
document.getElementById("SampleRate").textContent = e.additionalData.SampleRate;
document.getElementById("ChannelCount").textContent = e.additionalData.ChannelCount;
document.getElementById("ChannelConfig").textContent = e.additionalData.ChannelConfig;
}

// Called when the visualisation timer ticks.
function OnTimer(sampleCount, channelCount, sampleRate, channelConfig)
{
if (e.additionalData) // && e.additionalData.SampleCount == "bufferType1")
var L = 0, R = 0;

for (i = 0; i < sampleCount; i += channelCount)
{
Chunk = e.getBuffer();
L = Math.max(L, Samples[0]);
R = Math.max(R, Samples[1]);
}

document.getElementById("Timer").textContent = Date.now() + ": " + sampleCount + " samples, " + sampleRate + "Hz, " + channelCount + " channels (" + ("00000000" + channelConfig.toString(16)).substr(-8) + "), Left: " + L.toFixed(3) + ", Right: " + R.toFixed(3);
}
</script>
</body>
Expand Down
Loading

0 comments on commit 857bd77

Please sign in to comment.