├── README.md └── soundCloud.fx /README.md: -------------------------------------------------------------------------------- 1 | # SoundCloud Theme for Foobar2000 Waveform Seekbar Plugin 2 | ![](http://i.imgur.com/et9lsur.png) 3 | ##Requirements 4 | * [Waveform Seekbar](https://www.foobar2000.org/components/view/foo_wave_seekbar) plugin for foobar2000. 5 | * Video card with at least Shader model 3.0 support. 6 | 7 | ##Installation 8 | * Right click on the Waveform seekbar panel > Configure. 9 | * Choose the **Direct3D** frontend. 10 | * Click on **Frontend Settings…** and replace existing shader with the script from the [soundCloud.fx](https://github.com/madhex/soundcloud-fb2k-waveform-seekbar/blob/master/soundCloud.fx) file. 11 | * Chose the following **colors**: 12 | * Background color: RGB 255, 255, 255 13 | * Foreground color: RGB 255, 255, 255 14 | * Highlight color: RGB 255, 51, 0 15 | * Choose **Mix-down to mono** under Downmix Display. 16 | -------------------------------------------------------------------------------- /soundCloud.fx: -------------------------------------------------------------------------------- 1 | texture tex : WAVEFORMDATA; 2 | 3 | sampler sTex = sampler_state 4 | { 5 | Texture = (tex); 6 | MipFilter = LINEAR; 7 | MinFilter = LINEAR; 8 | MagFilter = LINEAR; 9 | AddressU = Clamp; 10 | }; 11 | 12 | struct VS_IN 13 | { 14 | float2 pos : POSITION; 15 | float2 tc : TEXCOORD0; 16 | }; 17 | 18 | struct PS_IN 19 | { 20 | float4 pos : SV_POSITION; 21 | float2 tc : TEXCOORD0; 22 | }; 23 | 24 | float4 panelBackgroundColor : BACKGROUNDCOLOR; // 'Background color' - whole panel background 25 | float4 waveHighlightColor : HIGHLIGHTCOLOR; // 'Highlight color' - for played part 26 | float4 selectionColor : SELECTIONCOLOR; // Not used 27 | float4 waveUnplayedColor : TEXTCOLOR; // 'Foreground color' - for unplayed part 28 | float cursorPos : CURSORPOSITION; 29 | bool cursorVisible : CURSORVISIBLE; 30 | float seekPos : SEEKPOSITION; 31 | bool seeking : SEEKING; 32 | float4 replayGain : REPLAYGAIN; 33 | float2 viewportSize : VIEWPORTSIZE; 34 | bool horizontal : ORIENTATION; 35 | bool flipped : FLIPPED; 36 | bool shadePlayed : SHADEPLAYED; 37 | 38 | float4 colorDodge(float4 baseColor, float4 blendColor) 39 | { 40 | return saturate(baseColor / (1 - blendColor)); 41 | } 42 | 43 | float4 linearDodge(float4 baseColor, float4 blendColor) 44 | { 45 | return saturate(baseColor + blendColor); 46 | } 47 | 48 | float4 multiply(float4 baseColor, float4 blendColor) 49 | { 50 | return saturate(baseColor * blendColor); 51 | } 52 | 53 | float4 getSeekAheadColor() 54 | { 55 | float4 color = colorDodge(waveHighlightColor, 0.4); 56 | color.a = 0.5; 57 | 58 | return color; 59 | } 60 | 61 | float4 getSeekBackColor() 62 | { 63 | return float4(1.0, 1.0, 1.0, 0.5); 64 | } 65 | 66 | float4 getWaveTopColor() 67 | { 68 | return colorDodge(waveHighlightColor, 0.57); 69 | } 70 | 71 | float4 getReflectionHightlightColor() 72 | { 73 | float4 color = waveHighlightColor; 74 | color = multiply(color, 0.75); 75 | color = linearDodge(color, 0.6); 76 | 77 | return color; 78 | } 79 | 80 | float2 getPixelSize() 81 | { 82 | float2 pixelSize = horizontal 83 | ? 1 / viewportSize.xy 84 | : 1 / viewportSize.yx; 85 | 86 | return pixelSize; 87 | } 88 | 89 | /* 90 | Split waveform into bars. 91 | 92 | Waveform texture contains upper half-waves (positive peaks) in Green channel 93 | and negative ones in Red. Negative values are ignored to make reflection later. 94 | */ 95 | float4 rmsSampling(float2 tc, float2 pixelSize, inout bool isGap) 96 | { 97 | float rmsData; 98 | float mainAxisLength = (horizontal ? viewportSize.x : viewportSize.y); 99 | 100 | if (ceil(mainAxisLength * tc.x) % 3 < 0.1) 101 | { 102 | rmsData = tex1D(sTex, tc.x).g; 103 | } 104 | else if (ceil(mainAxisLength * tc.x) % 3 < 1.1) 105 | { 106 | rmsData = tex1D(sTex, tc.x - pixelSize.x).g; 107 | } 108 | else 109 | { 110 | isGap = true; 111 | 112 | // Height of a gap equals to height of the smallest neighbour bar 113 | float leftPeak = tex1D(sTex, tc.x - (2 * pixelSize.x)).g; 114 | float rightPeak = tex1D(sTex, tc.x + pixelSize.x).g; 115 | rmsData = min(leftPeak, rightPeak); 116 | } 117 | 118 | return rmsData; 119 | } 120 | 121 | float4 getWaveColor(float2 tc, float2 pixelSize, int pixelBar, int cursorBar, float barCoverage, float4 bgColor, bool isGap) 122 | { 123 | bool isBeyondSeek = tc.x > seekPos; 124 | float4 seekAheadColor = getSeekAheadColor(); 125 | float4 seekBackColor = getSeekBackColor(); 126 | float4 seekColor = isBeyondSeek ? seekBackColor : seekAheadColor; 127 | 128 | float4 waveTop = getWaveTopColor(); 129 | float4 waveBottom = waveHighlightColor; 130 | 131 | float4 color = (cursorBar < pixelBar ? waveUnplayedColor : lerp(waveBottom, waveTop, tc.y)); 132 | 133 | if (pixelBar == cursorBar) 134 | { 135 | if (seeking) 136 | { 137 | if (isBeyondSeek) 138 | { 139 | color = lerp(color, seekColor, seekColor.a); 140 | color = lerp(color, waveUnplayedColor, barCoverage); 141 | } 142 | else 143 | { 144 | color = lerp(color, lerp(waveUnplayedColor, seekColor, seekColor.a), barCoverage); 145 | } 146 | } 147 | else 148 | { 149 | color = lerp(color, waveUnplayedColor, barCoverage); 150 | } 151 | } 152 | else if (seeking) 153 | { 154 | bool afterCursor = tc.x + pixelSize.x > cursorPos; 155 | float4 seekColor = isBeyondSeek ? seekBackColor : seekAheadColor; 156 | 157 | if ((afterCursor + isBeyondSeek) == 1) 158 | { 159 | color = lerp(color, seekColor, seekColor.a); 160 | } 161 | } 162 | 163 | if (isGap) 164 | { 165 | color = lerp(color, bgColor, tc.y * 0.35 + 0.5); 166 | } 167 | 168 | return color; 169 | } 170 | 171 | float4 getReflectionColor(float2 tc, float2 pixelSize, int pixelBar, int cursorBar, float barCoverage, float4 bgColor, bool isGap) 172 | { 173 | float4 color; 174 | 175 | float4 reflectionUnplayedColor = multiply(waveUnplayedColor, 0.8980); 176 | float4 reflectionHighlightColor = getReflectionHightlightColor(); 177 | 178 | if (isGap) 179 | { 180 | color = bgColor; 181 | } 182 | else if (pixelBar == cursorBar) 183 | { 184 | color = lerp(reflectionHighlightColor, reflectionUnplayedColor, barCoverage); 185 | } 186 | else 187 | { 188 | color = cursorPos < tc.x + pixelSize.x ? reflectionUnplayedColor : reflectionHighlightColor; 189 | } 190 | 191 | return color; 192 | } 193 | 194 | float4 evaluate(float2 tc) 195 | { 196 | float mainAxisLength = (horizontal ? viewportSize.x : viewportSize.y); 197 | float2 pixelSize = getPixelSize(); 198 | 199 | bool isGap = false; 200 | float rmsData = rmsSampling(tc, pixelSize, isGap); 201 | 202 | tc.y += 0.5; 203 | 204 | // Reflection 205 | if (tc.y < 0) 206 | { 207 | // Invert upper half-waves to imitate reflection 208 | rmsData = 1 - rmsData; 209 | 210 | // Squeeze reflection 211 | tc.y *= 3.5; 212 | } 213 | 214 | rmsData -= 0.5; 215 | rmsData *= 2.5; 216 | 217 | bool above = abs(tc.y) > abs(rmsData); 218 | float4 bgColor = panelBackgroundColor * ((0.93 - (tc.x * 0.08)) + ((tc.y * 0.1) - 0.07)); 219 | 220 | if (above || abs(tc.y) < 1.33 * pixelSize.y) 221 | { 222 | return bgColor; 223 | } 224 | 225 | int pixelBar = ceil(mainAxisLength * (tc.x + pixelSize.x) / 3.0); 226 | int cursorBar = ceil(mainAxisLength * cursorPos / 3.0); 227 | float barCoverage = (cursorBar * 3.0 - (mainAxisLength * cursorPos)) / 3.0; 228 | 229 | float4 color = tc.y > 0 230 | ? getWaveColor(tc, pixelSize, pixelBar, cursorBar, barCoverage, bgColor, isGap) 231 | : getReflectionColor(tc, pixelSize, pixelBar, cursorBar, barCoverage, bgColor, isGap); 232 | 233 | return color; 234 | } 235 | 236 | PS_IN VS(VS_IN input) 237 | { 238 | float pixelWidth = horizontal 239 | ? 1 / viewportSize.x 240 | : 1 / viewportSize.y; 241 | 242 | PS_IN output = (PS_IN)0; 243 | 244 | // Move left to fill 1st empty pixel 245 | output.pos = float4(input.pos - float2(pixelWidth, 0), 0, 1); 246 | 247 | if (horizontal) 248 | { 249 | float firstBarAlignment = (flipped ? 2*pixelWidth : -2*pixelWidth); 250 | output.tc = float4((input.tc.xy + float2(1.0 + firstBarAlignment, 0)) * float2(0.5, 1.0), 0, 1); 251 | } 252 | else 253 | { 254 | float firstBarAlignment = (flipped ? 3*pixelWidth : -1*pixelWidth); 255 | output.tc = float4((-input.pos.yx + float2(1.0 + firstBarAlignment, 0)) * float2(0.5, 1.0), 0, 1); 256 | } 257 | 258 | if (flipped) 259 | { 260 | output.tc.x = 1.0 - output.tc.x; 261 | } 262 | 263 | return output; 264 | } 265 | 266 | float4 PS(PS_IN input) : SV_Target 267 | { 268 | float4 color = evaluate(input.tc); 269 | 270 | return color; 271 | } 272 | 273 | technique Render9 274 | { 275 | pass 276 | { 277 | VertexShader = compile vs_2_0 VS(); 278 | PixelShader = compile ps_3_0 PS(); 279 | } 280 | } 281 | 282 | technique10 Render10 283 | { 284 | pass P0 285 | { 286 | SetGeometryShader(0); 287 | SetVertexShader(CompileShader(vs_4_0, VS())); 288 | SetPixelShader(CompileShader(ps_4_0, PS())); 289 | } 290 | } 291 | --------------------------------------------------------------------------------