Initial commit

This commit is contained in:
LastLife 2026-04-25 03:20:06 +02:00
parent d7833f6660
commit bb9287793b
3 changed files with 721 additions and 0 deletions

577
WaveSurferSB.sbi Normal file
View File

@ -0,0 +1,577 @@
; WaveSurferSB - A wavesurfer.js v7 wrapper for SpiderBasic
;
; This library wraps the wavesurfer.js audio waveform player for use in SpiderBasic.
; Includes support for Timeline, Minimap, and Spectrogram plugins.
; wavesurfer.js v7: https://wavesurfer.xyz / https://github.com/katspaugh/wavesurfer.js
;
; Usage:
; IncludeFile "WaveSurferSB/WaveSurferSB.sbi"
;
; ; Load core + plugins (comma-separated: "timeline,minimap,spectrogram")
; WaveSurferSB::Download(@MyCallback(), "timeline,minimap,spectrogram")
;
; Procedure MyCallback(Success)
; If Success
; Define ws = WaveSurferSB::Create("#myContainer", WaveSurferSB::#DragToSeek)
; WaveSurferSB::TimelineCreate(ws)
; WaveSurferSB::MinimapCreate(ws, 50, "#777", "#333")
; WaveSurferSB::SpectrogramCreate(ws, "", 128, #True)
; WaveSurferSB::Load(ws, "audio.mp3")
; EndIf
; EndProcedure
;
; For wavesurfer.js documentation, see: https://wavesurfer.xyz/docs
DeclareModule WaveSurferSB
;{ Initialization
; Plugins.s: comma-separated plugin names to load, e.g. "timeline,minimap,spectrogram"
Declare Download(*Callback, Plugins.s = "", UseLocalFiles = #False)
;}
;{ Instance Management
Declare Create(Container.s, Flags = 0)
Declare Create_ex(Container.s, OptionsJSON.s)
Declare Destroy(Instance)
Declare Empty(Instance)
;}
;{ Audio Loading
Declare Load(Instance, URL.s)
Declare LoadWithPeaks(Instance, URL.s, PeaksJSON.s, Duration.d = 0.0)
;}
;{ Playback Control
Declare Play(Instance)
Declare PlayFrom(Instance, StartSeconds.d, EndSeconds.d = -1.0)
Declare Pause(Instance)
Declare PlayPause(Instance)
Declare Stop(Instance)
Declare IsPlaying(Instance)
;}
;{ Navigation
Declare SetTime(Instance, TimeSeconds.d)
Declare.d GetCurrentTime(Instance)
Declare.d GetDuration(Instance)
Declare SeekTo(Instance, Progress.d)
;}
;{ Volume & Speed
Declare SetVolume(Instance, Volume.d)
Declare.d GetVolume(Instance)
Declare SetMuted(Instance, Muted)
Declare SetPlaybackRate(Instance, Rate.d)
Declare.d GetPlaybackRate(Instance)
;}
;{ Visual Options
Declare SetOptions(Instance, OptionsJSON.s)
Declare Zoom(Instance, MinPxPerSec.d)
;}
;{ Generic Plugin Registration
Declare RegisterPlugin(Instance, PluginName.s, OptionsJSON.s = "{}")
Declare DestroyPlugin(PluginInstance)
;}
;{ Timeline Plugin
Declare TimelineCreate(Instance, Container.s = "", Height = 20)
Declare TimelineCreate_ex(Instance, OptionsJSON.s)
Declare TimelineDestroy(PluginInstance)
;}
;{ Minimap Plugin
Declare MinimapCreate(Instance, Height = 50, WaveColor.s = "#999", ProgressColor.s = "#555", OverlayColor.s = "")
Declare MinimapCreate_ex(Instance, OptionsJSON.s)
Declare MinimapDestroy(PluginInstance)
;}
;{ Spectrogram Plugin
Declare SpectrogramCreate(Instance, Container.s = "", Height = 128, Labels = #False)
Declare SpectrogramCreate_ex(Instance, OptionsJSON.s)
Declare SpectrogramDestroy(PluginInstance)
;}
;{ Events
Declare OnReady(Instance, *Callback)
Declare OnPlay(Instance, *Callback)
Declare OnPause(Instance, *Callback)
Declare OnFinish(Instance, *Callback)
Declare OnTimeUpdate(Instance, *Callback)
Declare OnSeeking(Instance, *Callback)
Declare OnInteraction(Instance, *Callback)
Declare OnClick(Instance, *Callback)
Declare OnDecode(Instance, *Callback)
Declare OnLoading(Instance, *Callback)
Declare OnDestroy(Instance, *Callback)
Declare OnEvent(Instance, EventName.s, *Callback)
;}
;{ Data Export
Declare.s GetMediaElement(Instance)
;}
;{ Creation Flag Constants
#Default = 0
#Normalize = 1 << 0
#AutoPlay = 1 << 1
#DragToSeek = 1 << 2
#HideScrollbar = 1 << 3
#AutoScroll = 1 << 4
#AutoCenter = 1 << 5
#SplitChannels = 1 << 6
#BarStyle = 1 << 7
;}
EndDeclareModule
Module WaveSurferSB
EnableExplicit
;{ Private Variables
Global download_callback
Global is_loaded = #False
Global plugins_to_load.s = ""
Global use_local.i = #False
;}
;{ Private Declarations
Declare Handler_Download(url.s, success)
Declare Handler_PluginDownload(url.s, success)
Declare _HideAMD()
Declare _RestoreAMD()
Declare _LoadNextPlugin()
;}
; AMD/UMD Helpers
Procedure _HideAMD()
!window._ws_saved_module = (typeof module !== 'undefined') ? module : undefined;
!window._ws_saved_exports = (typeof exports !== 'undefined') ? exports : undefined;
!window._ws_saved_define = (typeof define !== 'undefined') ? define : undefined;
!try { module = undefined; } catch(e) {}
!try { exports = undefined; } catch(e) {}
!try { define = undefined; } catch(e) {}
EndProcedure
Procedure _RestoreAMD()
!try { if (window._ws_saved_module !== undefined) module = window._ws_saved_module; } catch(e) {}
!try { if (window._ws_saved_exports !== undefined) exports = window._ws_saved_exports; } catch(e) {}
!try { if (window._ws_saved_define !== undefined) define = window._ws_saved_define; } catch(e) {}
EndProcedure
; Plugin Loading Chain
Procedure _LoadNextPlugin()
Protected plugin.s, url.s
; Pop the first plugin from the comma-separated list
!v_plugin = wavesurfersb$g_plugins_to_load.split(',')[0] || '';
!wavesurfersb$g_plugins_to_load = wavesurfersb$g_plugins_to_load.split(',').slice(1).join(',');
!v_plugin = v_plugin.trim();
Protected check
!v_check = (v_plugin === '');
If check
; All plugins loaded - restore AMD and call user callback
_RestoreAMD()
is_loaded = #True
!wavesurfersb$g_download_callback(1);
ProcedureReturn
EndIf
If use_local
url = "LocalFiles/JS/wavesurfer-" + plugin + ".min.js"
Else
url = "https://unpkg.com/wavesurfer.js@7/dist/plugins/" + plugin + ".min.js"
EndIf
LoadScript(url, @Handler_PluginDownload(), #PB_Script_JavaScript)
EndProcedure
; Initialization
Procedure Download(*Callback, Plugins.s = "", UseLocalFiles = #False)
download_callback = *Callback
plugins_to_load = Plugins
use_local = UseLocalFiles
_HideAMD()
If UseLocalFiles
LoadScript("LocalFiles/JS/wavesurfer.min.js", @Handler_Download(), #PB_Script_JavaScript)
Else
LoadScript("https://unpkg.com/wavesurfer.js@7/dist/wavesurfer.min.js", @Handler_Download(), #PB_Script_JavaScript)
EndIf
EndProcedure
; Instance Management
Procedure Create(Container.s, Flags = 0)
Protected Instance
; Build options object in JavaScript
!var _opts = { container: v_container };
If Flags & #Normalize
!_opts.normalize = true;
EndIf
If Flags & #AutoPlay
!_opts.autoplay = true;
EndIf
If Flags & #DragToSeek
!_opts.dragToSeek = true;
EndIf
If Flags & #HideScrollbar
!_opts.hideScrollbar = true;
EndIf
If Flags & #AutoScroll
!_opts.autoScroll = true;
EndIf
If Flags & #AutoCenter
!_opts.autoCenter = true;
EndIf
If Flags & #SplitChannels
!_opts.splitChannels = [{}];
EndIf
If Flags & #BarStyle
!_opts.barWidth = 2;
!_opts.barGap = 1;
!_opts.barRadius = 2;
EndIf
!v_instance = WaveSurfer.create(_opts);
ProcedureReturn Instance
EndProcedure
Procedure Create_ex(Container.s, OptionsJSON.s)
Protected Instance
!var _opts;
!try { _opts = JSON.parse(v_optionsjson); } catch(e) { _opts = {}; }
!_opts.container = v_container;
!v_instance = WaveSurfer.create(_opts);
ProcedureReturn Instance
EndProcedure
Procedure Destroy(Instance)
!if (v_instance && typeof v_instance.destroy === 'function') {
! v_instance.destroy();
!}
EndProcedure
Procedure Empty(Instance)
!if (v_instance && typeof v_instance.empty === 'function') {
! v_instance.empty();
!}
EndProcedure
; Audio Loading
Procedure Load(Instance, URL.s)
!if (v_instance) v_instance.load(v_url);
EndProcedure
Procedure LoadWithPeaks(Instance, URL.s, PeaksJSON.s, Duration.d = 0.0)
!if (v_instance) {
! var _peaks;
! try { _peaks = JSON.parse(v_peaksjson); } catch(e) { _peaks = undefined; }
! var _dur = v_duration > 0 ? v_duration : undefined;
! v_instance.load(v_url, _peaks, _dur);
!}
EndProcedure
; Playback Control
Procedure Play(Instance)
!if (v_instance) v_instance.play();
EndProcedure
Procedure PlayFrom(Instance, StartSeconds.d, EndSeconds.d = -1.0)
!if (v_instance) {
! if (v_endseconds >= 0) {
! v_instance.play(v_startseconds, v_endseconds);
! } else {
! v_instance.play(v_startseconds);
! }
!}
EndProcedure
Procedure Pause(Instance)
!if (v_instance) v_instance.pause();
EndProcedure
Procedure PlayPause(Instance)
!if (v_instance) v_instance.playPause();
EndProcedure
Procedure Stop(Instance)
!if (v_instance) v_instance.stop();
EndProcedure
Procedure IsPlaying(Instance)
Protected Result = #False
!if (v_instance) v_result = v_instance.isPlaying();
ProcedureReturn Result
EndProcedure
; Navigation
Procedure SetTime(Instance, TimeSeconds.d)
!if (v_instance) v_instance.setTime(v_timeseconds);
EndProcedure
Procedure.d GetCurrentTime(Instance)
Protected.d Result = 0.0
!if (v_instance) v_result = v_instance.getCurrentTime();
ProcedureReturn Result
EndProcedure
Procedure.d GetDuration(Instance)
Protected.d Result = 0.0
!if (v_instance) v_result = v_instance.getDuration();
ProcedureReturn Result
EndProcedure
Procedure SeekTo(Instance, Progress.d)
!if (v_instance) v_instance.seekTo(v_progress);
EndProcedure
; Volume & Speed
Procedure SetVolume(Instance, Volume.d)
!if (v_instance) v_instance.setVolume(v_volume);
EndProcedure
Procedure.d GetVolume(Instance)
Protected.d Result = 1.0
!if (v_instance) v_result = v_instance.getVolume();
ProcedureReturn Result
EndProcedure
Procedure SetMuted(Instance, Muted)
!if (v_instance) v_instance.setMuted(!!v_muted);
EndProcedure
Procedure SetPlaybackRate(Instance, Rate.d)
!if (v_instance) v_instance.setPlaybackRate(v_rate);
EndProcedure
Procedure.d GetPlaybackRate(Instance)
Protected.d Result = 1.0
!if (v_instance) v_result = v_instance.getPlaybackRate();
ProcedureReturn Result
EndProcedure
; Visual Options
Procedure SetOptions(Instance, OptionsJSON.s)
!if (v_instance) {
! var _opts;
! try { _opts = JSON.parse(v_optionsjson); } catch(e) { _opts = {}; }
! v_instance.setOptions(_opts);
!}
EndProcedure
Procedure Zoom(Instance, MinPxPerSec.d)
!if (v_instance) v_instance.zoom(v_minpxpersec);
EndProcedure
; Generic Plugin Registration
Procedure RegisterPlugin(Instance, PluginName.s, OptionsJSON.s = "{}")
Protected PluginInstance
!if (v_instance && WaveSurfer[v_pluginname]) {
! var _opts;
! try { _opts = JSON.parse(v_optionsjson); } catch(e) { _opts = {}; }
! v_plugininstance = v_instance.registerPlugin(WaveSurfer[v_pluginname].create(_opts));
!}
ProcedureReturn PluginInstance
EndProcedure
Procedure DestroyPlugin(PluginInstance)
!if (v_plugininstance && typeof v_plugininstance.destroy === 'function') {
! v_plugininstance.destroy();
!}
EndProcedure
; Timeline Plugin
Procedure TimelineCreate(Instance, Container.s = "", Height = 20)
Protected PluginInstance
!if (v_instance && WaveSurfer.Timeline) {
! var _opts = { height: v_height };
! if (v_container && v_container !== '') _opts.container = v_container;
! v_plugininstance = v_instance.registerPlugin(WaveSurfer.Timeline.create(_opts));
!}
ProcedureReturn PluginInstance
EndProcedure
Procedure TimelineCreate_ex(Instance, OptionsJSON.s)
Protected PluginInstance
!if (v_instance && WaveSurfer.Timeline) {
! var _opts;
! try { _opts = JSON.parse(v_optionsjson); } catch(e) { _opts = {}; }
! v_plugininstance = v_instance.registerPlugin(WaveSurfer.Timeline.create(_opts));
!}
ProcedureReturn PluginInstance
EndProcedure
Procedure TimelineDestroy(PluginInstance)
DestroyPlugin(PluginInstance)
EndProcedure
; Minimap Plugin
Procedure MinimapCreate(Instance, Height = 50, WaveColor.s = "#999", ProgressColor.s = "#555", OverlayColor.s = "")
Protected PluginInstance
!if (v_instance && WaveSurfer.Minimap) {
! var _opts = {
! height: v_height,
! waveColor: v_wavecolor,
! progressColor: v_progresscolor
! };
! if (v_overlaycolor && v_overlaycolor !== '') _opts.overlayColor = v_overlaycolor;
! v_plugininstance = v_instance.registerPlugin(WaveSurfer.Minimap.create(_opts));
!}
ProcedureReturn PluginInstance
EndProcedure
Procedure MinimapCreate_ex(Instance, OptionsJSON.s)
Protected PluginInstance
!if (v_instance && WaveSurfer.Minimap) {
! var _opts;
! try { _opts = JSON.parse(v_optionsjson); } catch(e) { _opts = {}; }
! v_plugininstance = v_instance.registerPlugin(WaveSurfer.Minimap.create(_opts));
!}
ProcedureReturn PluginInstance
EndProcedure
Procedure MinimapDestroy(PluginInstance)
DestroyPlugin(PluginInstance)
EndProcedure
; Spectrogram Plugin
Procedure SpectrogramCreate(Instance, Container.s = "", Height = 128, Labels = #False)
Protected PluginInstance
!if (v_instance && WaveSurfer.Spectrogram) {
! var _opts = {
! height: v_height,
! labels: !!v_labels
! };
! if (v_container && v_container !== '') _opts.container = v_container;
! v_plugininstance = v_instance.registerPlugin(WaveSurfer.Spectrogram.create(_opts));
!}
ProcedureReturn PluginInstance
EndProcedure
Procedure SpectrogramCreate_ex(Instance, OptionsJSON.s)
Protected PluginInstance
!if (v_instance && WaveSurfer.Spectrogram) {
! var _opts;
! try { _opts = JSON.parse(v_optionsjson); } catch(e) { _opts = {}; }
! v_plugininstance = v_instance.registerPlugin(WaveSurfer.Spectrogram.create(_opts));
!}
ProcedureReturn PluginInstance
EndProcedure
Procedure SpectrogramDestroy(PluginInstance)
DestroyPlugin(PluginInstance)
EndProcedure
; Events
Procedure OnReady(Instance, *Callback)
!if (v_instance) v_instance.on('ready', function(duration) { p_callback(duration); });
EndProcedure
Procedure OnPlay(Instance, *Callback)
!if (v_instance) v_instance.on('play', function() { p_callback(); });
EndProcedure
Procedure OnPause(Instance, *Callback)
!if (v_instance) v_instance.on('pause', function() { p_callback(); });
EndProcedure
Procedure OnFinish(Instance, *Callback)
!if (v_instance) v_instance.on('finish', function() { p_callback(); });
EndProcedure
Procedure OnTimeUpdate(Instance, *Callback)
!if (v_instance) v_instance.on('timeupdate', function(currentTime) { p_callback(currentTime); });
EndProcedure
Procedure OnSeeking(Instance, *Callback)
!if (v_instance) v_instance.on('seeking', function(currentTime) { p_callback(currentTime); });
EndProcedure
Procedure OnInteraction(Instance, *Callback)
!if (v_instance) v_instance.on('interaction', function(newTime) { p_callback(newTime); });
EndProcedure
Procedure OnClick(Instance, *Callback)
!if (v_instance) v_instance.on('click', function(relativeX) { p_callback(relativeX); });
EndProcedure
Procedure OnDecode(Instance, *Callback)
!if (v_instance) v_instance.on('decode', function(duration) { p_callback(duration); });
EndProcedure
Procedure OnLoading(Instance, *Callback)
!if (v_instance) v_instance.on('loading', function(percent) { p_callback(percent); });
EndProcedure
Procedure OnDestroy(Instance, *Callback)
!if (v_instance) v_instance.on('destroy', function() { p_callback(); });
EndProcedure
Procedure OnEvent(Instance, EventName.s, *Callback)
!if (v_instance) v_instance.on(v_eventname, function() { p_callback(); });
EndProcedure
; Data Export
Procedure.s GetMediaElement(Instance)
Protected Result.s = ""
!if (v_instance) {
! var _media = v_instance.getMediaElement();
! if (_media) v_result = _media.outerHTML || '';
!}
ProcedureReturn Result
EndProcedure
; Private Procedures
Procedure Handler_Download(url.s, success)
If success
; Core loaded - now load plugins if any
If plugins_to_load <> ""
_LoadNextPlugin()
Else
; No plugins requested - all done
_RestoreAMD()
is_loaded = #True
!wavesurfersb$g_download_callback(1);
EndIf
Else
_RestoreAMD()
!wavesurfersb$g_download_callback(0);
EndIf
EndProcedure
Procedure Handler_PluginDownload(url.s, success)
If success
; Plugin loaded - continue chain with next plugin
_LoadNextPlugin()
Else
_RestoreAMD()
!wavesurfersb$g_download_callback(0);
EndIf
EndProcedure
EndModule
; IDE Options = SpiderBasic 3.20 (Windows - x86)
; Folding = BAAAAAAAAAAg
; iOSAppOrientation = 0
; AndroidAppCode = 0
; AndroidAppOrientation = 0
; EnableXP
; DPIAware
; CompileSourceDirectory

View File

@ -0,0 +1,144 @@
; WaveSurferSB Plugins Example
; Demonstrates Timeline, Minimap, and Spectrogram plugins
;
; This example loads wavesurfer.js core + all three plugins,
; creates a waveform with playback controls, and attaches each plugin.
IncludeFile "WaveSurferSB.sbi"
Global ws ; WaveSurfer instance
Global wsTimeline ; Timeline plugin instance
Global wsMinimap ; Minimap plugin instance
Global wsSpectro ; Spectrogram plugin instance
Enumeration
#btnPlay
#btnPause
#btnStop
#txtStatus
#txtTime
EndEnumeration
; ---- Event Callbacks ----
Procedure OnReady(Duration.d)
Debug "Audio ready! Duration: " + StrD(Duration, 2) + "s"
SetGadgetText(#txtStatus, "Ready - " + StrD(Duration, 1) + "s")
EndProcedure
Procedure OnTimeUpdate(CurrentTime.d)
SetGadgetText(#txtTime, StrD(CurrentTime, 1) + " / " + StrD(WaveSurferSB::GetDuration(ws), 1))
EndProcedure
Procedure OnFinish()
SetGadgetText(#txtStatus, "Finished")
EndProcedure
; ---- Button Events ----
Procedure OnEvent()
Select EventGadget()
Case #btnPlay
WaveSurferSB::Play(ws)
SetGadgetText(#txtStatus, "Playing")
Case #btnPause
WaveSurferSB::Pause(ws)
SetGadgetText(#txtStatus, "Paused")
Case #btnStop
WaveSurferSB::Stop(ws)
SetGadgetText(#txtStatus, "Stopped")
EndSelect
EndProcedure
; ---- Download Callback ----
Procedure OnDownloadComplete(Success)
If Not Success
Debug "ERROR: Failed to load wavesurfer.js or plugins"
SetGadgetText(#txtStatus, "Load failed!")
ProcedureReturn
EndIf
Debug "wavesurfer.js + plugins loaded successfully"
; Create the main WaveSurfer instance with drag-to-seek and bar style
ws = WaveSurferSB::Create("#waveform", WaveSurferSB::#DragToSeek | WaveSurferSB::#BarStyle)
; ---- Attach Plugins ----
; Timeline: time labels below the waveform (default container, 20px height)
wsTimeline = WaveSurferSB::TimelineCreate(ws)
; Minimap: overview navigation bar with colored overlay
wsMinimap = WaveSurferSB::MinimapCreate(ws, 40, "#888", "#444", "rgba(100,100,200,0.3)")
; Spectrogram: frequency visualization with labels
; Renders into the #spectrogram div, 150px tall, with frequency labels
wsSpectro = WaveSurferSB::SpectrogramCreate(ws, "#spectrogram", 150, #True)
; ---- Advanced plugin example using _ex (JSON options) ----
; If you need full control, use the _ex variants instead:
;
; wsTimeline = WaveSurferSB::TimelineCreate_ex(ws, ~"{\"height\": 25, \"timeInterval\": 5, \"primaryLabelInterval\": 10, \"insertPosition\": \"beforebegin\"}")
;
; wsSpectro = WaveSurferSB::SpectrogramCreate_ex(ws, ~"{\"container\": \"#spectrogram\", \"height\": 200, \"labels\": true, \"fftSamples\": 1024, \"scale\": \"mel\", \"colorMap\": \"roseus\"}")
; ---- Bind Events ----
WaveSurferSB::OnReady(ws, @OnReady())
WaveSurferSB::OnTimeUpdate(ws, @OnTimeUpdate())
WaveSurferSB::OnFinish(ws, @OnFinish())
; ---- Load Audio ----
; Replace with your audio URL
WaveSurferSB::Load(ws, "WaveSurferTestMusic.ogg")
SetGadgetText(#txtStatus, "Loading audio...")
EndProcedure
; ---- Main UI ----
Procedure Main()
OpenWindow(0, 0, 0, 800, 600, "WaveSurferSB - Plugins Demo", #PB_Window_ScreenCentered)
; Create HTML containers for the waveform and spectrogram
; The waveform div holds the main wave + timeline + minimap (auto-appended)
; The spectrogram div is separate so it renders below
!var _wsRoot = document.body.firstElementChild || document.body;
!_wsRoot.insertAdjacentHTML('afterbegin',
! '<div id="waveform" style="width:100%; margin:10px 0; border:1px solid #ccc; border-radius:4px; overflow:hidden;"></div>' +
! '<div id="spectrogram" style="width:100%; margin:10px 0;"></div>'
!);
; Transport controls
ButtonGadget(#btnPlay, 10, 420, 80, 30, "Play")
ButtonGadget(#btnPause, 100, 420, 80, 30, "Pause")
ButtonGadget(#btnStop, 190, 420, 80, 30, "Stop")
TextGadget(#txtStatus, 290, 425, 200, 25, "Loading...")
TextGadget(#txtTime, 500, 425, 200, 25, "0.0 / 0.0")
BindGadgetEvent(#btnPlay, @OnEvent())
BindGadgetEvent(#btnPause, @OnEvent())
BindGadgetEvent(#btnStop, @OnEvent())
; Download core + all three plugins (loaded sequentially)
; Plugin names must match the filenames on unpkg CDN
WaveSurferSB::Download(@OnDownloadComplete(), "timeline,minimap,spectrogram")
EndProcedure
Main()
; IDE Options = SpiderBasic 3.20 (Windows - x86)
; CursorPosition = 94
; FirstLine = 57
; Folding = --
; iOSAppOrientation = 0
; AndroidAppCode = 0
; AndroidAppOrientation = 0
; EnableXP
; DPIAware
; CompileSourceDirectory

BIN
WaveSurferTestMusic.ogg Normal file

Binary file not shown.