diff --git a/ClassicalSharp/ClassicalSharp.csproj b/ClassicalSharp/ClassicalSharp.csproj
index bda21043f..90cbee10e 100644
--- a/ClassicalSharp/ClassicalSharp.csproj
+++ b/ClassicalSharp/ClassicalSharp.csproj
@@ -71,6 +71,9 @@
+
+ SharpWave.dll
+
@@ -243,6 +246,9 @@
PreserveNewest
+
+ PreserveNewest
+
diff --git a/ClassicalSharp/Particles/CollidableParticle.cs b/ClassicalSharp/Particles/CollidableParticle.cs
new file mode 100644
index 000000000..a74a04b2e
--- /dev/null
+++ b/ClassicalSharp/Particles/CollidableParticle.cs
@@ -0,0 +1,66 @@
+using System;
+using ClassicalSharp.Entities;
+using OpenTK;
+
+namespace ClassicalSharp.Particles {
+
+ public abstract class CollidableParticle : Particle {
+
+ const float gravity = 3.4f;
+ //const float gravity = 0.7f; // TODO: temp debug
+
+ public CollidableParticle( Game game, Vector3 pos, Vector3 velocity, double lifetime )
+ : base( game, pos, velocity, lifetime ) {
+ }
+
+ public override bool Tick( double delta ) {
+ lastPos = Position = nextPos;
+ byte curBlock = game.World.SafeGetBlock( (int)Position.X, (int)Position.Y, (int)Position.Z );
+ if( !CanPassThrough( curBlock ) ) return true;
+
+ Velocity.Y -= gravity * (float)delta;
+ int startY = (int)Math.Floor( Position.Y );
+ Position += Velocity * (float)delta * 3;
+ int endY = (int)Math.Floor( Position.Y );
+ Utils.Clamp( ref Position.X, 0, game.World.Width - 0.01f );
+ Utils.Clamp( ref Position.Z, 0, game.World.Length - 0.01f );
+
+ if( Velocity.Y > 0 ) {
+ for( int y = startY; y <= endY && TestY( y, false ); y++ );
+ } else {
+ for( int y = startY; y >= endY && TestY( y, true ); y-- );
+ }
+ nextPos = Position;
+ Position = lastPos;
+ return base.Tick( delta );
+ }
+
+ bool TestY( int y, bool topFace ) {
+ if( y < 0 ) {
+ Position.Y = nextPos.Y = lastPos.Y = 0 + Entity.Adjustment;
+ Velocity = Vector3.Zero;
+ return false;
+ }
+
+ byte block = game.World.SafeGetBlock( (int)Position.X, y, (int)Position.Z );
+ if( CanPassThrough( block ) ) return true;
+
+ float collideY = y;
+ if( topFace )
+ collideY += game.BlockInfo.Height[block];
+
+ bool collide = topFace ? (Position.Y < collideY) : (Position.Y > collideY );
+ if( collide ) {
+ float adjust = topFace ? Entity.Adjustment : -Entity.Adjustment;
+ Position.Y = nextPos.Y = lastPos.Y = collideY + adjust;
+ Velocity = Vector3.Zero;
+ return false;
+ }
+ return true;
+ }
+
+ bool CanPassThrough( byte block ) {
+ return block == 0 || game.BlockInfo.IsSprite[block] || game.BlockInfo.IsLiquid[block];
+ }
+ }
+}
diff --git a/ClassicalSharp/Particles/Particle.cs b/ClassicalSharp/Particles/Particle.cs
new file mode 100644
index 000000000..6ff57d9f9
--- /dev/null
+++ b/ClassicalSharp/Particles/Particle.cs
@@ -0,0 +1,28 @@
+using System;
+using OpenTK;
+
+namespace ClassicalSharp.Particles {
+
+ public abstract class Particle {
+
+ public Vector3 Position;
+ public Vector3 Velocity;
+ public float Lifetime;
+ protected Game game;
+ protected Vector3 lastPos, nextPos;
+
+ public abstract void Render( double delta, float t, VertexPos3fTex2fCol4b[] vertices, ref int index );
+
+ public Particle( Game game, Vector3 pos, Vector3 velocity, double lifetime ) {
+ this.game = game;
+ Position = lastPos = nextPos = pos;
+ Velocity = velocity;
+ Lifetime = (float)lifetime;
+ }
+
+ public virtual bool Tick( double delta ) {
+ Lifetime -= (float)delta;
+ return Lifetime < 0;
+ }
+ }
+}
\ No newline at end of file
diff --git a/ClassicalSharp/Particles/ParticleManager.cs b/ClassicalSharp/Particles/ParticleManager.cs
new file mode 100644
index 000000000..115206830
--- /dev/null
+++ b/ClassicalSharp/Particles/ParticleManager.cs
@@ -0,0 +1,129 @@
+using System;
+using System.Collections.Generic;
+using ClassicalSharp.GraphicsAPI;
+using OpenTK;
+
+namespace ClassicalSharp.Particles {
+
+ public class ParticleManager : IDisposable {
+
+ public int ParticlesTexId;
+ List terrainParticles = new List();
+ List rainParticles = new List();
+ Game game;
+ Random rnd;
+ int vb;
+
+ public ParticleManager( Game game ) {
+ this.game = game;
+ rnd = new Random();
+ vb = game.Graphics.CreateDynamicVb( VertexFormat.Pos3fTex2fCol4b, 1000 );
+ }
+
+ VertexPos3fTex2fCol4b[] vertices = new VertexPos3fTex2fCol4b[0];
+ public void Render( double delta, float t ) {
+ if( terrainParticles.Count == 0 && rainParticles.Count == 0 ) return;
+ IGraphicsApi graphics = game.Graphics;
+ graphics.Texturing = true;
+ graphics.AlphaTest = true;
+ graphics.SetBatchFormat( VertexFormat.Pos3fTex2fCol4b );
+
+ int count = RenderParticles( terrainParticles, delta, t );
+ if( count > 0 ) {
+ graphics.BindTexture( game.TerrainAtlas.TexId );
+ graphics.UpdateDynamicIndexedVb( DrawMode.Triangles, vb, vertices, count, count * 6 / 4 );
+ }
+ count = RenderParticles( rainParticles, delta, t );
+ if( count > 0 ) {
+ graphics.BindTexture( ParticlesTexId );
+ graphics.UpdateDynamicIndexedVb( DrawMode.Triangles, vb, vertices, count, count * 6 / 4 );
+ }
+
+ graphics.AlphaTest = false;
+ graphics.Texturing = false;
+ }
+
+ int RenderParticles( List particles, double delta, float t ) {
+ int count = particles.Count * 4;
+ if( count > vertices.Length )
+ vertices = new VertexPos3fTex2fCol4b[count];
+
+ int index = 0;
+ for( int i = 0; i < particles.Count; i++ )
+ particles[i].Render( delta, t, vertices, ref index );
+ return Math.Min( count, 1000 );
+ }
+
+ public void Tick( double delta ) {
+ TickParticles( terrainParticles, delta );
+ TickParticles( rainParticles, delta );
+ }
+
+ void TickParticles( List particles, double delta ) {
+ for( int i = 0; i < particles.Count; i++ ) {
+ Particle particle = particles[i];
+ if( particle.Tick( delta ) ) {
+ particles.RemoveAt( i );
+ i--;
+ }
+ }
+ }
+
+ public void Dispose() {
+ game.Graphics.DeleteDynamicVb( vb );
+ game.Graphics.DeleteTexture( ref ParticlesTexId );
+ }
+
+ public void BreakBlockEffect( Vector3I position, byte block ) {
+ Vector3 startPos = new Vector3( position.X, position.Y, position.Z );
+ int texLoc = game.BlockInfo.GetTextureLoc( block, TileSide.Left );
+ TextureRec rec = game.TerrainAtlas.GetTexRec( texLoc );
+
+ const float invSize = TerrainAtlas2D.invElementSize;
+ const int cellsCount = (int)((1/4f) / invSize);
+ const float elemSize = invSize / 4f;
+ float blockHeight = game.BlockInfo.Height[block];
+
+ for( int i = 0; i < 25; i++ ) {
+ double velX = rnd.NextDouble() * 0.8 - 0.4; // [-0.4, 0.4]
+ double velZ = rnd.NextDouble() * 0.8 - 0.4;
+ double velY = rnd.NextDouble() + 0.2;
+ Vector3 velocity = new Vector3( (float)velX, (float)velY, (float)velZ );
+
+ double xOffset = rnd.NextDouble() - 0.5; // [-0.5, 0.5]
+ double yOffset = (rnd.NextDouble() - 0.125) * blockHeight;
+ double zOffset = rnd.NextDouble() - 0.5;
+ Vector3 pos = startPos + new Vector3( 0.5f + (float)xOffset,
+ (float)yOffset, 0.5f + (float)zOffset );
+
+ TextureRec particleRec = rec;
+ particleRec.U1 = rec.U1 + rnd.Next( 0, cellsCount ) * elemSize;
+ particleRec.V1 = rec.V1 + rnd.Next( 0, cellsCount ) * elemSize;
+ particleRec.U2 = particleRec.U1 + elemSize;
+ particleRec.V2 = particleRec.V1 + elemSize;
+ double life = 1.5 - rnd.NextDouble();
+
+ terrainParticles.Add( new TerrainParticle( game, pos, velocity, life, particleRec ) );
+ }
+ }
+
+ public void AddRainParticle( Vector3 pos ) {
+ Vector3 startPos = pos;
+
+ for( int i = 0; i < 5; i++ ) {
+ double velX = rnd.NextDouble() * 0.8 - 0.4; // [-0.4, 0.4]
+ double velZ = rnd.NextDouble() * 0.8 - 0.4;
+ double velY = rnd.NextDouble() + 0.5;
+ Vector3 velocity = new Vector3( (float)velX, (float)velY, (float)velZ );
+
+ double xOffset = rnd.NextDouble() - 0.5; // [-0.5, 0.5]
+ double yOffset = 0.01;
+ double zOffset = rnd.NextDouble() - 0.5;
+ pos = startPos + new Vector3( 0.5f + (float)xOffset,
+ (float)yOffset, 0.5f + (float)zOffset );
+ double life = 3.5 - rnd.NextDouble();
+ rainParticles.Add( new RainParticle( game, pos, velocity, life ) );
+ }
+ }
+ }
+}
diff --git a/ClassicalSharp/Particles/RainParticle.cs b/ClassicalSharp/Particles/RainParticle.cs
new file mode 100644
index 000000000..abfdef5f4
--- /dev/null
+++ b/ClassicalSharp/Particles/RainParticle.cs
@@ -0,0 +1,28 @@
+using System;
+using OpenTK;
+
+namespace ClassicalSharp.Particles {
+
+ public sealed class RainParticle : CollidableParticle {
+
+ static Vector2 rainSize = new Vector2( 1/8f, 1/8f );
+ static TextureRec rec = new TextureRec( 2/128f, 14/128f, 3/128f, 2/128f );
+ public RainParticle( Game game, Vector3 pos, Vector3 velocity, double lifetime )
+ : base( game, pos, velocity, lifetime ) {
+ }
+
+ public override void Render( double delta, float t, VertexPos3fTex2fCol4b[] vertices, ref int index ) {
+ Position = Vector3.Lerp( lastPos, nextPos, t );
+ Vector3 p111, p121, p212, p222;
+ Utils.CalcBillboardPoints( rainSize, Position, ref game.View,
+ out p111, out p121, out p212, out p222 );
+ World map = game.World;
+ FastColour col = map.IsLit( Vector3I.Floor( Position ) ) ? map.Sunlight : map.Shadowlight;
+
+ vertices[index++] = new VertexPos3fTex2fCol4b( p111, rec.U1, rec.V2, col );
+ vertices[index++] = new VertexPos3fTex2fCol4b( p121, rec.U1, rec.V1, col );
+ vertices[index++] = new VertexPos3fTex2fCol4b( p222, rec.U2, rec.V1, col );
+ vertices[index++] = new VertexPos3fTex2fCol4b( p212, rec.U2, rec.V2, col );
+ }
+ }
+}
diff --git a/ClassicalSharp/Particles/TerrainParticle.cs b/ClassicalSharp/Particles/TerrainParticle.cs
new file mode 100644
index 000000000..2ec081577
--- /dev/null
+++ b/ClassicalSharp/Particles/TerrainParticle.cs
@@ -0,0 +1,29 @@
+using System;
+using OpenTK;
+
+namespace ClassicalSharp.Particles {
+
+ public sealed class TerrainParticle : CollidableParticle {
+
+ static Vector2 terrainSize = new Vector2( 1/8f, 1/8f );
+ TextureRec rec;
+ public TerrainParticle( Game game, Vector3 pos, Vector3 velocity, double lifetime, TextureRec rec )
+ : base( game, pos, velocity, lifetime ) {
+ this.rec = rec;
+ }
+
+ public override void Render( double delta, float t, VertexPos3fTex2fCol4b[] vertices, ref int index ) {
+ Position = Vector3.Lerp( lastPos, nextPos, t );
+ Vector3 p111, p121, p212, p222;
+ Utils.CalcBillboardPoints( terrainSize, Position, ref game.View,
+ out p111, out p121, out p212, out p222 );
+ World map = game.World;
+ FastColour col = map.IsLit( Vector3I.Floor( Position ) ) ? map.Sunlight : map.Shadowlight;
+
+ vertices[index++] = new VertexPos3fTex2fCol4b( p111, rec.U1, rec.V2, col );
+ vertices[index++] = new VertexPos3fTex2fCol4b( p121, rec.U1, rec.V1, col );
+ vertices[index++] = new VertexPos3fTex2fCol4b( p222, rec.U2, rec.V1, col );
+ vertices[index++] = new VertexPos3fTex2fCol4b( p212, rec.U2, rec.V2, col );
+ }
+ }
+}
diff --git a/ClassicalSharp/SharpWave.dll b/ClassicalSharp/SharpWave.dll
new file mode 100644
index 000000000..021aa796b
Binary files /dev/null and b/ClassicalSharp/SharpWave.dll differ
diff --git a/ClassicalSharp/SharpWave.dll.config b/ClassicalSharp/SharpWave.dll.config
new file mode 100644
index 000000000..a4e4f7b60
--- /dev/null
+++ b/ClassicalSharp/SharpWave.dll.config
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/Launcher2/Gui/Screens/ResourcesScreen.cs b/Launcher2/Gui/Screens/ResourcesScreen.cs
index d58eef229..b1681a077 100644
--- a/Launcher2/Gui/Screens/ResourcesScreen.cs
+++ b/Launcher2/Gui/Screens/ResourcesScreen.cs
@@ -48,10 +48,9 @@ namespace Launcher2 {
game.Downloader = new AsyncDownloader( "null" );
if( fetcher != null ) return;
- fetcher = new ResourceFetcher( game.Downloader );
- fetcher.DownloadItems( SetStatus );
- selectedWidget = null;
-
+ fetcher = game.fetcher;
+ fetcher.DownloadItems( game.Downloader, SetStatus );
+ selectedWidget = null;
Resize();
}
@@ -59,7 +58,7 @@ namespace Launcher2 {
static FastColour backCol = new FastColour( 120, 85, 151 );
static readonly string mainText = "Some required resources weren't found" +
Environment.NewLine + "Okay to download them?";
- static readonly string format = "Estimated size: {0} megabytes";
+ static readonly string format = "Download size: {0} megabytes";
static FastColour clearCol = new FastColour( 12, 12, 12 );
void Draw() {
@@ -68,8 +67,9 @@ namespace Launcher2 {
drawer.DrawRect( backCol, game.Width / 2 - 175,
game.Height / 2 - 70, 175 * 2, 70 * 2 );
+ float dataSize = game.fetcher.DownloadSize;
string text = widgets[0] != null ? widgets[0].Text
- : String.Format( format, ResourceFetcher.EstimateDownloadSize() );
+ : String.Format( format, dataSize.ToString( "F2" ) );
MakeLabelAt( text, statusFont, Anchor.Centre, Anchor.Centre, 0, 5 );
// Clear the entire previous widgets state.
diff --git a/Launcher2/Launcher2.csproj b/Launcher2/Launcher2.csproj
index 9b9fba424..2849587f0 100644
--- a/Launcher2/Launcher2.csproj
+++ b/Launcher2/Launcher2.csproj
@@ -45,6 +45,9 @@
obj\
+
+ ..\ClassicalSharp\SharpWave.dll
+
@@ -72,6 +75,7 @@
+
diff --git a/Launcher2/LauncherWindow.cs b/Launcher2/LauncherWindow.cs
index cd186110d..7ac4c3436 100644
--- a/Launcher2/LauncherWindow.cs
+++ b/Launcher2/LauncherWindow.cs
@@ -48,6 +48,8 @@ namespace Launcher2 {
public Dictionary> ScreenMetadata =
new Dictionary>();
+ internal ResourceFetcher fetcher;
+
Font logoFont, logoItalicFont;
PlatformDrawer platformDrawer;
public void Init() {
@@ -120,7 +122,9 @@ namespace Launcher2 {
TryLoadTexturePack();
platformDrawer.Init( Window.WindowInfo );
- if( !ResourceFetcher.CheckAllResourcesExist() ) {
+ fetcher = new ResourceFetcher();
+ fetcher.CheckResourceExistence();
+ if( !fetcher.AllResourcesExist ) {
SetScreen( new ResourcesScreen( this ) );
} else {
SetScreen( new MainScreen( this ) );
diff --git a/Launcher2/Patcher/ResourceFetcher.cs b/Launcher2/Patcher/ResourceFetcher.cs
index f6a884e4b..0c519e588 100644
--- a/Launcher2/Patcher/ResourceFetcher.cs
+++ b/Launcher2/Patcher/ResourceFetcher.cs
@@ -7,17 +7,22 @@ namespace Launcher2 {
public sealed class ResourceFetcher {
public bool Done = false;
- AsyncDownloader downloader;
- public ResourceFetcher( AsyncDownloader downloader ) {
- this.downloader = downloader;
+ internal AsyncDownloader downloader;
+ public ResourceFetcher() {
+ digPath = Path.Combine( "audio", "dig" );
+ stepPath = Path.Combine( "audio", "step" );
}
const string jarClassicUri = "http://s3.amazonaws.com/Minecraft.Download/versions/c0.30_01c/c0.30_01c.jar";
const string jar162Uri = "http://s3.amazonaws.com/Minecraft.Download/versions/1.6.2/1.6.2.jar";
const string pngTerrainPatchUri = "http://static.classicube.net/terrain-patch.png";
const string pngGuiPatchUri = "http://static.classicube.net/gui.png";
+ const string digSoundsUri = "http://s3.amazonaws.com/MinecraftResources/sound3/dig/";
+ const string stepSoundsUri = "http://s3.amazonaws.com/MinecraftResources/sound3/step/";
+ const string musicUri = "http://s3.amazonaws.com/MinecraftResources/music/";
- public void DownloadItems( Action setStatus ) {
+ public void DownloadItems( AsyncDownloader downloader, Action setStatus ) {
+ this.downloader = downloader;
downloader.DownloadData( jarClassicUri, false, "classic_jar" );
downloader.DownloadData( jar162Uri, false, "162_jar" );
downloader.DownloadData( pngTerrainPatchUri, false, "terrain_patch" );
@@ -69,14 +74,50 @@ namespace Launcher2 {
return true;
}
- public static bool CheckAllResourcesExist() {
- return File.Exists( "default.zip" );
+ public void CheckResourceExistence() {
+ AllResourcesExist = File.Exists( "default.zip" );
+ //&& Directory.Exists( "audio" ) && File.Exists( digPath + ".bin" )
+ //&& File.Exists( stepPath + ".bin" );
+
+ if( !File.Exists( "default.zip" ) ) {
+ // classic.jar + 1.6.2.jar + terrain-patch.png + gui.png
+ DownloadSize += (291 + 4621 + 7 + 21) / 1024f;
+ ResourcesCount += 4;
+ }
+
+ for( int i = 0; i < musicFiles.Length; i++ ) {
+ string file = Path.Combine( "audio", musicFiles[i] + ".ogg" );
+ musicExists[i] = File.Exists( file );
+ continue;
+ // TODO: download music files
+ if( !musicExists[i] ) {
+ DownloadSize += musicSizes[i] / 1024f;
+ ResourcesCount++;
+ }
+ }
+ ResourcesCount += digSounds.Length;
+ ResourcesCount += stepSounds.Length;
}
+ public bool AllResourcesExist;
+ public float DownloadSize;
+ public int ResourcesCount;
- public static string EstimateDownloadSize() {
- float size = (291 + 4621 + 7 + 21) / 1024f;
- // classic.jar + 1.6.2.jar + terrain-patch.png + gui.png
- return size.ToString( "F2" );
- }
+ string digPath, stepPath;
+ string[] digSounds = new [] { "cloth1", "cloth2", "cloth3", "cloth4",
+ "glass1", "glass2", "glass3", "glass4", "grass1", "grass2", "grass3",
+ "grass4", "gravel1", "gravel2", "gravel3", "gravel4", "sand1", "sand2",
+ "sand3", "sand4", "snow1", "snow2", "snow3", "snow4", "stone1", "stone2",
+ "stone3", "stone4", "wood1", "wood2", "wood3", "wood4" };
+
+ string[] stepSounds = new [] { "cloth1", "cloth2", "cloth3", "cloth4", "grass1",
+ "grass2", "grass3", "grass4", "grass5", "grass6", "gravel1", "gravel2",
+ "gravel3", "gravel4", "sand1", "sand2", "sand3", "sand4", "sand5", "snow1",
+ "snow2", "snow3", "snow4", "stone1", "stone2", "stone3", "stone4", "stone5",
+ "stone6", "wood1", "wood2", "wood3", "wood4", "wood5", "wood6" };
+
+ string[] musicFiles = new [] { "calm1", "calm2",
+ "calm3", "hal1", "hal2", "hal3" };
+ int[] musicSizes = new [] { 2472, 1931, 2181, 1926, 1714, 1879, 2499 };
+ bool[] musicExists = new bool[6];
}
}
diff --git a/Launcher2/Patcher/SoundPatcher.cs b/Launcher2/Patcher/SoundPatcher.cs
new file mode 100644
index 000000000..3596610d3
--- /dev/null
+++ b/Launcher2/Patcher/SoundPatcher.cs
@@ -0,0 +1,88 @@
+using System;
+using System.IO;
+using ClassicalSharp.Network;
+using SharpWave;
+using SharpWave.Codecs;
+using SharpWave.Codecs.Vorbis;
+using SharpWave.Containers;
+
+namespace Launcher2 {
+
+ public sealed class SoundPatcher {
+
+ string[] files, identifiers;
+ string prefix;
+ FileStream outData;
+ StreamWriter outText;
+ RawOut outDecoder;
+
+ public SoundPatcher( string[] files, string prefix, string outputPath ) {
+ this.files = files;
+ this.prefix = prefix;
+ InitOutput( outputPath );
+ }
+
+ public void FetchFiles( string baseUrl, ResourceFetcher fetcher ) {
+ identifiers = new string[files.Length];
+ for( int i = 0; i < files.Length; i++ )
+ identifiers[i] = prefix + files[i];
+
+ for( int i = 0; i < files.Length; i++ ) {
+ string url = baseUrl + files[i] + ".ogg";
+ fetcher.downloader.DownloadData( url, false, identifiers[i] );
+ }
+ }
+
+ public bool CheckDownloaded( ResourceFetcher fetcher, Action setStatus ) {
+ for( int i = 0; i < identifiers.Length; i++ ) {
+ DownloadedItem item;
+ if( fetcher.downloader.TryGetItem( identifiers[i], out item ) ) {
+ Console.WriteLine( "found sound " + identifiers[i] );
+ if( item.Data == null ) {
+ setStatus( "&cFailed to download " + identifiers[i] );
+ return false;
+ }
+ DecodeSound( files[i], (byte[])item.Data );
+
+ // TODO: setStatus( next );
+ if( i == identifiers.Length - 1 ) {
+ Dispose();
+ }
+ }
+ }
+ return true;
+ }
+
+ void DecodeSound( string name, byte[] rawData ) {
+ long start = outData.Position;
+ using( MemoryStream ms = new MemoryStream( rawData ) ) {
+ OggContainer container = new OggContainer( ms );
+ outDecoder.PlayStreaming( container );
+ }
+
+ long len = outData.Position - start;
+ outText.WriteLine( format, name, outDecoder.Frequency,
+ outDecoder.BitsPerSample, outDecoder.Channels,
+ start, len );
+ }
+
+ const string format = "{0},{1},{2},{3},{4},{5}";
+ void InitOutput( string outputPath ) {
+ outData = File.Create( outputPath + ".bin" );
+ outText = new StreamWriter( outputPath + ".txt" );
+ outDecoder = new RawOut( outData, true );
+
+ outText.WriteLine( "# This file indicates where the various raw decompressed sound data " +
+ "are found in the corresponding raw .bin file." );
+ outText.WriteLine( "# Each line is in the following format:" );
+ outText.WriteLine( "# Identifier, Frequency/Sample rate, BitsPerSample, " +
+ "Channels, Offset from start, Length in bytes" );
+ }
+
+ void Dispose() {
+ outDecoder.Dispose();
+ outData.Close();
+ outText.Close();
+ }
+ }
+}