From b1a972a9056c740a2d32a7518c9fb749313ff41c Mon Sep 17 00:00:00 2001 From: UnknownShadow200 Date: Fri, 27 Nov 2015 07:22:29 +1100 Subject: [PATCH] Add SoundPatcher and refactor ResourceFetcher, in preparation for fetching sound/music resources. --- ClassicalSharp/ClassicalSharp.csproj | 6 + .../Particles/CollidableParticle.cs | 66 +++++++++ ClassicalSharp/Particles/Particle.cs | 28 ++++ ClassicalSharp/Particles/ParticleManager.cs | 129 ++++++++++++++++++ ClassicalSharp/Particles/RainParticle.cs | 28 ++++ ClassicalSharp/Particles/TerrainParticle.cs | 29 ++++ ClassicalSharp/SharpWave.dll | Bin 0 -> 77824 bytes ClassicalSharp/SharpWave.dll.config | 4 + Launcher2/Gui/Screens/ResourcesScreen.cs | 12 +- Launcher2/Launcher2.csproj | 4 + Launcher2/LauncherWindow.cs | 6 +- Launcher2/Patcher/ResourceFetcher.cs | 63 +++++++-- Launcher2/Patcher/SoundPatcher.cs | 88 ++++++++++++ 13 files changed, 445 insertions(+), 18 deletions(-) create mode 100644 ClassicalSharp/Particles/CollidableParticle.cs create mode 100644 ClassicalSharp/Particles/Particle.cs create mode 100644 ClassicalSharp/Particles/ParticleManager.cs create mode 100644 ClassicalSharp/Particles/RainParticle.cs create mode 100644 ClassicalSharp/Particles/TerrainParticle.cs create mode 100644 ClassicalSharp/SharpWave.dll create mode 100644 ClassicalSharp/SharpWave.dll.config create mode 100644 Launcher2/Patcher/SoundPatcher.cs 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 0000000000000000000000000000000000000000..021aa796b2125c986f9055896a510d77caa98ba0 GIT binary patch literal 77824 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 literal 0 HcmV?d00001 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(); + } + } +}