Incremental work
This commit is contained in:
parent
9d2bb9fc65
commit
803415c988
.gitignoreMakeAPK.projectbuild_demo.bat
BNA
Demo1
Demo1
Demo1Content
Demo1FSharp
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,3 +2,4 @@
|
||||
.vs/
|
||||
packages/
|
||||
/TODO
|
||||
mybuild.bat
|
||||
|
@ -14,6 +14,7 @@
|
||||
<ItemGroup>
|
||||
<Reference Include="$(ObjDir)Android.dll" />
|
||||
<Reference Include="$(FNA_DLL)" />
|
||||
<Reference Include="System.dll" />
|
||||
<Compile Include="src\**\*.cs" />
|
||||
<CustomAdditionalCompileInputs Include="FNA.filter" />
|
||||
<Filter Include="FNA.filter" />
|
||||
|
@ -102,3 +102,11 @@
|
||||
|
||||
*.Content.*
|
||||
*.Storage.*
|
||||
|
||||
*.Audio.AudioChannels
|
||||
*.Audio.SoundState
|
||||
*.Audio.*Exception
|
||||
|
||||
*.Media.MediaQueue
|
||||
*.Media.MediaState
|
||||
*.Media.SongCollection
|
||||
|
29
BNA/lzxdecoder.license
Normal file
29
BNA/lzxdecoder.license
Normal file
@ -0,0 +1,29 @@
|
||||
LzxDecoder.cs was derived from libmspack
|
||||
Copyright 2003-2004 Stuart Caie
|
||||
Copyright 2011 Ali Scissons
|
||||
|
||||
The LZX method was created by Jonathan Forbes and Tomi Poutanen, adapted
|
||||
by Microsoft Corporation.
|
||||
|
||||
This source file is Dual licensed; meaning the end-user of this source file
|
||||
may redistribute/modify it under the LGPL 2.1 or MS-PL licenses.
|
||||
|
||||
About
|
||||
-----
|
||||
This derived work is recognized by Stuart Caie and is authorized to adapt
|
||||
any changes made to lzxd.c in his libmspack library and will still retain
|
||||
this dual licensing scheme. Big thanks to Stuart Caie!
|
||||
|
||||
This file is a pure C# port of the lzxd.c file from libmspack, with minor
|
||||
changes towards the decompression of XNB files. The original decompression
|
||||
software of LZX encoded data was written by Suart Caie in his
|
||||
libmspack/cabextract projects, which can be located at
|
||||
http://http://www.cabextract.org.uk/
|
||||
|
||||
GNU Lesser General Public License, Version 2.1
|
||||
----------------------------------------------
|
||||
https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
|
||||
|
||||
Microsoft Public License
|
||||
------------------------
|
||||
http://www.opensource.org/licenses/ms-pl.html
|
22
BNA/monoxna.license
Normal file
22
BNA/monoxna.license
Normal file
@ -0,0 +1,22 @@
|
||||
MIT License
|
||||
Copyright 2006 The Mono.Xna Team
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
@ -22,7 +22,6 @@ namespace Microsoft.Xna.Framework.Graphics
|
||||
}
|
||||
|
||||
|
||||
|
||||
//
|
||||
// CreateProgram
|
||||
//
|
||||
|
@ -37,7 +37,8 @@ namespace Microsoft.Xna.Framework.Graphics
|
||||
if ((state.TargetFramebuffer = id[0]) == 0)
|
||||
return;
|
||||
}
|
||||
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, state.TargetFramebuffer);
|
||||
GLES20.glBindFramebuffer(GLES30.GL_DRAW_FRAMEBUFFER,
|
||||
state.TargetFramebuffer);
|
||||
|
||||
int attachmentIndex = GLES20.GL_COLOR_ATTACHMENT0;
|
||||
foreach (var renderTarget in renderTargetsCopy)
|
||||
@ -48,7 +49,7 @@ namespace Microsoft.Xna.Framework.Graphics
|
||||
// from FNA3D_GetMaxMultiSampleCount, which we never do
|
||||
throw new PlatformNotSupportedException();
|
||||
/*GLES20.glFramebufferRenderbuffer(
|
||||
GLES20.GL_FRAMEBUFFER, attachmentIndex,
|
||||
GLES20.GL_DRAW_FRAMEBUFFER, attachmentIndex,
|
||||
GLES20.GL_RENDERBUFFER, (int) renderTarget.colorBuffer);*/
|
||||
}
|
||||
else
|
||||
@ -60,7 +61,7 @@ namespace Microsoft.Xna.Framework.Graphics
|
||||
+ renderTarget.data2;
|
||||
}
|
||||
GLES20.glFramebufferTexture2D(
|
||||
GLES20.GL_FRAMEBUFFER, attachmentIndex,
|
||||
GLES30.GL_DRAW_FRAMEBUFFER, attachmentIndex,
|
||||
attachmentType, (int) renderTarget.texture, 0);
|
||||
}
|
||||
attachmentIndex++;
|
||||
@ -71,12 +72,12 @@ namespace Microsoft.Xna.Framework.Graphics
|
||||
while (attachmentIndex < lastAttachmentPlusOne)
|
||||
{
|
||||
GLES20.glFramebufferRenderbuffer(
|
||||
GLES20.GL_FRAMEBUFFER, attachmentIndex++,
|
||||
GLES30.GL_DRAW_FRAMEBUFFER, attachmentIndex++,
|
||||
GLES20.GL_RENDERBUFFER, 0);
|
||||
}
|
||||
|
||||
GLES20.glFramebufferRenderbuffer(
|
||||
GLES20.GL_FRAMEBUFFER, GLES30.GL_DEPTH_STENCIL_ATTACHMENT,
|
||||
GLES30.GL_DRAW_FRAMEBUFFER, GLES30.GL_DEPTH_STENCIL_ATTACHMENT,
|
||||
GLES20.GL_RENDERBUFFER, (int) depthStencilBuffer);
|
||||
|
||||
state.RenderToTexture = true;
|
||||
@ -100,7 +101,7 @@ namespace Microsoft.Xna.Framework.Graphics
|
||||
var renderer = Renderer.Get(device);
|
||||
renderer.Send( () =>
|
||||
{
|
||||
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
|
||||
GLES20.glBindFramebuffer(GLES30.GL_DRAW_FRAMEBUFFER, 0);
|
||||
|
||||
var state = (State) renderer.UserData;
|
||||
state.RenderToTexture = false;
|
||||
@ -138,7 +139,10 @@ namespace Microsoft.Xna.Framework.Graphics
|
||||
|
||||
GLES20.glBindTexture(attachmentType, texture);
|
||||
GLES20.glGenerateMipmap(attachmentType);
|
||||
GLES20.glBindTexture(attachmentType, 0);
|
||||
|
||||
var state = (State) renderer.UserData;
|
||||
if (state.TextureOnLastUnit != 0)
|
||||
GLES20.glBindTexture(attachmentType, state.TextureOnLastUnit);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -204,6 +208,75 @@ namespace Microsoft.Xna.Framework.Graphics
|
||||
return ((State) Renderer.Get(graphicsDevice.GLDevice).UserData).RenderToTexture;
|
||||
}
|
||||
|
||||
//
|
||||
// Get Texture Data
|
||||
//
|
||||
|
||||
private static void GetTextureData(Renderer renderer, int textureId,
|
||||
int x, int y, int w, int h, int level,
|
||||
object dataObject, int dataOffset, int dataLength)
|
||||
{
|
||||
java.nio.Buffer buffer = dataObject switch
|
||||
{
|
||||
sbyte[] byteArray =>
|
||||
java.nio.ByteBuffer.wrap(byteArray, dataOffset, dataLength),
|
||||
|
||||
int[] intArray =>
|
||||
java.nio.IntBuffer.wrap(intArray, dataOffset / 4, dataLength / 4),
|
||||
|
||||
_ => throw new ArgumentException(dataObject?.GetType().ToString()),
|
||||
};
|
||||
|
||||
renderer.Send( () =>
|
||||
{
|
||||
var state = (State) renderer.UserData;
|
||||
if (state.SourceFramebuffer == 0)
|
||||
{
|
||||
var id = new int[1];
|
||||
GLES20.glGenFramebuffers(1, id, 0);
|
||||
if ((state.SourceFramebuffer = id[0]) == 0)
|
||||
return;
|
||||
}
|
||||
|
||||
var config = state.TextureConfigs[textureId];
|
||||
if (config[1] != (int) SurfaceFormat.Color)
|
||||
{
|
||||
throw new NotSupportedException(
|
||||
((SurfaceFormat) config[1]).ToString());
|
||||
}
|
||||
|
||||
GLES20.glBindFramebuffer(GLES30.GL_READ_FRAMEBUFFER,
|
||||
state.SourceFramebuffer);
|
||||
|
||||
GLES20.glFramebufferTexture2D(
|
||||
GLES30.GL_READ_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,
|
||||
GLES20.GL_TEXTURE_2D, textureId, level);
|
||||
|
||||
GLES20.glReadPixels(x, y, w, h,
|
||||
GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buffer);
|
||||
|
||||
GLES20.glBindFramebuffer(GLES30.GL_READ_FRAMEBUFFER, 0);
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
// FNA3D_GetTextureData2D
|
||||
//
|
||||
|
||||
public static void FNA3D_GetTextureData2D(IntPtr device, IntPtr texture,
|
||||
int x, int y, int w, int h, int level,
|
||||
IntPtr data, int dataLength)
|
||||
{
|
||||
// FNA Texture2D uses GCHandle::Alloc and GCHandle::AddrOfPinnedObject.
|
||||
// we use GCHandle::FromIntPtr to convert that address to an object reference.
|
||||
// see also: system.runtime.interopservices.GCHandle struct in baselib.
|
||||
int dataOffset = (int) data;
|
||||
var dataObject = System.Runtime.InteropServices.GCHandle.FromIntPtr(data).Target;
|
||||
|
||||
GetTextureData(Renderer.Get(device), (int) texture,
|
||||
x, y, w, h, level, dataObject, dataOffset, dataLength);
|
||||
}
|
||||
|
||||
//
|
||||
// DepthFormatToDepthStorage
|
||||
//
|
||||
@ -238,6 +311,7 @@ namespace Microsoft.Xna.Framework.Graphics
|
||||
private partial class State
|
||||
{
|
||||
public bool RenderToTexture;
|
||||
public int SourceFramebuffer;
|
||||
public int TargetFramebuffer;
|
||||
public int ActiveAttachments;
|
||||
}
|
||||
|
@ -257,7 +257,7 @@ namespace Microsoft.Xna.Framework.Graphics
|
||||
{
|
||||
string reason = (bitmap == null) ? "unspecified error"
|
||||
: $"unsupported config '{bitmap.getConfig()}'";
|
||||
throw new System.BadImageFormatException(
|
||||
throw new BadImageFormatException(
|
||||
$"Load failed for bitmap image '{titleStream.Name}': {reason}");
|
||||
}
|
||||
|
||||
@ -341,15 +341,22 @@ namespace Microsoft.Xna.Framework.Graphics
|
||||
ref FNA3D_SamplerState sampler)
|
||||
{
|
||||
var samplerCopy = sampler;
|
||||
int textureId = (int) texture;
|
||||
var renderer = Renderer.Get(device);
|
||||
|
||||
renderer.Send( () =>
|
||||
{
|
||||
var state = (State) renderer.UserData;
|
||||
var config = state.TextureConfigs[(int) texture];
|
||||
var config = state.TextureConfigs[textureId];
|
||||
|
||||
GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + index);
|
||||
GLES20.glBindTexture(config[0], (int) texture);
|
||||
GLES20.glBindTexture(config[0], textureId);
|
||||
|
||||
if (index == renderer.TextureUnits - 1)
|
||||
state.TextureOnLastUnit = textureId;
|
||||
|
||||
if (textureId == 0)
|
||||
return;
|
||||
|
||||
GLES20.glTexParameteri(config[0], GLES30.GL_TEXTURE_MAX_LEVEL,
|
||||
config[2] - 1);
|
||||
@ -571,6 +578,8 @@ namespace Microsoft.Xna.Framework.Graphics
|
||||
// #1 - SurfaceFormat
|
||||
// #2 - levels count
|
||||
public Dictionary<int, int[]> TextureConfigs = new Dictionary<int, int[]>();
|
||||
|
||||
public int TextureOnLastUnit;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -226,3 +226,24 @@ namespace Microsoft.Xna.Framework.Input.Touch
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
namespace Microsoft.Xna.Framework.Media
|
||||
{
|
||||
|
||||
//
|
||||
// MediaQueue
|
||||
//
|
||||
|
||||
[java.attr.Discard] // discard in output
|
||||
public class MediaQueue
|
||||
{
|
||||
public MediaQueue() { }
|
||||
public Song ActiveSong { get; }
|
||||
public int ActiveSongIndex { get; set; }
|
||||
public void Add(Song song) { }
|
||||
public void Clear() { }
|
||||
}
|
||||
|
||||
}
|
||||
|
331
BNA/src/MediaPlayer.cs
Normal file
331
BNA/src/MediaPlayer.cs
Normal file
@ -0,0 +1,331 @@
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
#pragma warning disable 0436
|
||||
|
||||
namespace Microsoft.Xna.Framework.Media
|
||||
{
|
||||
|
||||
public static class MediaPlayer
|
||||
{
|
||||
|
||||
[java.attr.RetainType] private static android.media.MediaPlayer player;
|
||||
[java.attr.RetainType] private static MediaQueue queue;
|
||||
[java.attr.RetainType] private static java.util.concurrent.atomic.AtomicInteger state;
|
||||
[java.attr.RetainType] private static float volume;
|
||||
[java.attr.RetainType] private static bool muted;
|
||||
[java.attr.RetainType] private static bool looping;
|
||||
|
||||
//
|
||||
// static constructor
|
||||
//
|
||||
|
||||
static MediaPlayer()
|
||||
{
|
||||
volume = 1f;
|
||||
state = new java.util.concurrent.atomic.AtomicInteger(0);
|
||||
queue = new MediaQueue();
|
||||
var watcher = new Watcher();
|
||||
player = new android.media.MediaPlayer();
|
||||
player.setOnPreparedListener(watcher);
|
||||
player.setOnCompletionListener(watcher);
|
||||
}
|
||||
|
||||
//
|
||||
// PrepareAndStart
|
||||
//
|
||||
|
||||
private static void PrepareAndStart()
|
||||
{
|
||||
int oldState = state.getAndSet((int) MediaState.Playing);
|
||||
player.prepareAsync();
|
||||
if (oldState != (int) MediaState.Playing && MediaStateChanged != null)
|
||||
{
|
||||
MediaStateChanged(null, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Play
|
||||
//
|
||||
|
||||
public static void Play(Song song)
|
||||
{
|
||||
if (ActiveSongChanged != null)
|
||||
throw new PlatformNotSupportedException("ActiveSongChanged");
|
||||
|
||||
if (song.isAsset)
|
||||
{
|
||||
var asset = GameRunner.Singleton.Activity.getAssets().openFd(song.path);
|
||||
player.setDataSource(asset.getFileDescriptor(),
|
||||
asset.getStartOffset(), asset.getLength());
|
||||
}
|
||||
else
|
||||
player.setDataSource(song.path);
|
||||
|
||||
Queue.Clear();
|
||||
Queue.Add(song);
|
||||
Queue.ActiveSongIndex = 0;
|
||||
|
||||
PrepareAndStart();
|
||||
}
|
||||
|
||||
//
|
||||
// Play (SongCollection)
|
||||
//
|
||||
|
||||
public static void Play(SongCollection songs, int index)
|
||||
{
|
||||
if (songs.Count == 0)
|
||||
{
|
||||
Queue.Clear();
|
||||
Stop();
|
||||
}
|
||||
else if (songs.Count == 1)
|
||||
{
|
||||
Play((Song) (object) songs[0]);
|
||||
}
|
||||
else
|
||||
throw new PlatformNotSupportedException();
|
||||
}
|
||||
|
||||
public static void Play(SongCollection songs) => Play(songs, 0);
|
||||
|
||||
//
|
||||
// Pause
|
||||
//
|
||||
|
||||
public static void Pause()
|
||||
{
|
||||
if (State == MediaState.Playing)
|
||||
{
|
||||
if (player.isPlaying())
|
||||
player.pause();
|
||||
State = MediaState.Paused;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Resume
|
||||
//
|
||||
|
||||
public static void Resume()
|
||||
{
|
||||
if (State == MediaState.Paused)
|
||||
{
|
||||
player.start();
|
||||
State = MediaState.Playing;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Stop
|
||||
//
|
||||
|
||||
public static void Stop()
|
||||
{
|
||||
if (State != MediaState.Stopped)
|
||||
{
|
||||
player.stop();
|
||||
State = MediaState.Stopped;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// MoveNext, MovePrevious
|
||||
//
|
||||
|
||||
public static void MoveNext()
|
||||
{
|
||||
Stop();
|
||||
if (looping)
|
||||
PrepareAndStart();
|
||||
}
|
||||
|
||||
public static void MovePrevious() => MoveNext();
|
||||
|
||||
//
|
||||
// IsMuted, Volume
|
||||
//
|
||||
|
||||
public static bool IsMuted
|
||||
{
|
||||
get => muted;
|
||||
set
|
||||
{
|
||||
muted = value;
|
||||
Volume = volume;
|
||||
}
|
||||
}
|
||||
|
||||
public static float Volume
|
||||
{
|
||||
get => volume;
|
||||
set
|
||||
{
|
||||
if (value < 0f)
|
||||
value = 0f;
|
||||
else if (value > 1f)
|
||||
value = 1f;
|
||||
volume = value;
|
||||
|
||||
if (muted)
|
||||
value = 0f;
|
||||
player.setVolume(value, value);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool GameHasControl => true;
|
||||
public static bool IsShuffled { get; set; }
|
||||
|
||||
//
|
||||
// IsRepeating
|
||||
//
|
||||
|
||||
public static bool IsRepeating
|
||||
{
|
||||
get => looping;
|
||||
set
|
||||
{
|
||||
if (value != looping)
|
||||
{
|
||||
looping = value;
|
||||
player.setLooping(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// State
|
||||
//
|
||||
|
||||
public static MediaState State
|
||||
{
|
||||
get => (MediaState) state.get();
|
||||
set
|
||||
{
|
||||
if ( state.getAndSet((int) value) != (int) value
|
||||
&& MediaStateChanged != null)
|
||||
{
|
||||
MediaStateChanged(null, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// PlayPosition
|
||||
//
|
||||
|
||||
public static TimeSpan PlayPosition
|
||||
=> TimeSpan.FromMilliseconds(player.getCurrentPosition());
|
||||
|
||||
//
|
||||
// Properties
|
||||
//
|
||||
|
||||
public static event EventHandler<EventArgs> ActiveSongChanged;
|
||||
public static event EventHandler<EventArgs> MediaStateChanged;
|
||||
|
||||
public static MediaQueue Queue => queue;
|
||||
|
||||
//
|
||||
// IsVisualizationEnabled
|
||||
//
|
||||
|
||||
public static bool IsVisualizationEnabled
|
||||
{
|
||||
get => false;
|
||||
set => throw new PlatformNotSupportedException();
|
||||
}
|
||||
|
||||
//
|
||||
// Watcher
|
||||
//
|
||||
|
||||
private class Watcher : android.media.MediaPlayer.OnPreparedListener,
|
||||
android.media.MediaPlayer.OnCompletionListener
|
||||
{
|
||||
|
||||
//
|
||||
// onPrepared
|
||||
//
|
||||
|
||||
[java.attr.RetainName]
|
||||
public void onPrepared(android.media.MediaPlayer player)
|
||||
{
|
||||
if (MediaPlayer.State == MediaState.Playing)
|
||||
{
|
||||
try
|
||||
{
|
||||
var duration = player.getDuration();
|
||||
if (duration != -1)
|
||||
{
|
||||
MediaPlayer.Queue.ActiveSong.Duration =
|
||||
TimeSpan.FromMilliseconds(duration);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
|
||||
player.start();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// onCompletion
|
||||
//
|
||||
|
||||
[java.attr.RetainName]
|
||||
public void onCompletion(android.media.MediaPlayer player)
|
||||
{
|
||||
MediaPlayer.Stop();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Song
|
||||
//
|
||||
|
||||
public sealed class Song : IEquatable<Song>, IDisposable
|
||||
{
|
||||
[java.attr.RetainType] public string path;
|
||||
[java.attr.RetainType] public bool isAsset;
|
||||
|
||||
public bool IsDisposed { get; private set; }
|
||||
public string Name { get; private set; }
|
||||
public TimeSpan Duration { get; set; }
|
||||
public bool IsProtected => false;
|
||||
public bool IsRated => false;
|
||||
public int PlayCount => 0;
|
||||
public int Rating => 0;
|
||||
public int TrackNumber => 0;
|
||||
|
||||
public static Song FromUri(string name, Uri uri)
|
||||
{
|
||||
var song = new Song() { Name = name };
|
||||
if (uri.IsAbsoluteUri && uri.IsFile)
|
||||
song.path = uri.LocalPath;
|
||||
else
|
||||
{
|
||||
song.path = uri.ToString();
|
||||
song.isAsset = true;
|
||||
}
|
||||
return song;
|
||||
}
|
||||
|
||||
~Song() => Dispose();
|
||||
public void Dispose() => IsDisposed = true;
|
||||
|
||||
public override int GetHashCode() => base.GetHashCode();
|
||||
public bool Equals(Song other) => (((object) other) != null) && (path == other.path);
|
||||
public override bool Equals(object other) => Equals(other as Song);
|
||||
public static bool operator ==(Song song1, Song song2)
|
||||
=> (song1 == null) ? (song2 == null) : song1.Equals(song2);
|
||||
public static bool operator !=(Song song1, Song song2) => ! (song1 == song2);
|
||||
|
||||
}
|
||||
|
||||
}
|
631
BNA/src/SoundEffect.cs
Normal file
631
BNA/src/SoundEffect.cs
Normal file
@ -0,0 +1,631 @@
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
#pragma warning disable 0436
|
||||
|
||||
namespace Microsoft.Xna.Framework.Audio
|
||||
{
|
||||
|
||||
public sealed class SoundEffect : IDisposable
|
||||
{
|
||||
|
||||
[java.attr.RetainType] public object dataArray;
|
||||
[java.attr.RetainType] public int dataCount;
|
||||
[java.attr.RetainType] public int sampleRate;
|
||||
[java.attr.RetainType] public int channelConfig;
|
||||
[java.attr.RetainType] public int markerFrame;
|
||||
|
||||
[java.attr.RetainType] public static java.util.ArrayList instancesList = new java.util.ArrayList();
|
||||
[java.attr.RetainType] public static java.util.concurrent.locks.ReentrantLock instancesLock = new java.util.concurrent.locks.ReentrantLock();
|
||||
|
||||
//
|
||||
// Constructor (for ContentReader)
|
||||
//
|
||||
|
||||
public SoundEffect(string name, byte[] buffer, int offset, int count,
|
||||
ushort wFormatTag, ushort nChannels,
|
||||
uint nSamplesPerSec, uint nAvgBytesPerSec,
|
||||
ushort nBlockAlign, ushort wBitsPerSample,
|
||||
int loopStart, int loopLength)
|
||||
{
|
||||
if (wFormatTag != 1 /* WAVE_FORMAT_PCM */)
|
||||
throw new ArgumentException("bad wFormatTag");
|
||||
if (offset != 0)
|
||||
throw new ArgumentException("bad offset");
|
||||
if (nBlockAlign != nChannels * wBitsPerSample / 8)
|
||||
throw new ArgumentException("bad nBlockAlign");
|
||||
if (nAvgBytesPerSec != nSamplesPerSec * nBlockAlign)
|
||||
throw new ArgumentException("bad nAvgBytesPerSec");
|
||||
|
||||
sampleRate = (int) nSamplesPerSec;
|
||||
channelConfig = (nChannels == 1) ? android.media.AudioFormat.CHANNEL_OUT_MONO
|
||||
: (nChannels == 2) ? android.media.AudioFormat.CHANNEL_OUT_STEREO
|
||||
: throw new ArgumentException("bad nChannels");
|
||||
|
||||
if (wBitsPerSample == 8)
|
||||
{
|
||||
dataArray = buffer;
|
||||
dataCount = count;
|
||||
}
|
||||
else if (wBitsPerSample == 16)
|
||||
{
|
||||
int shortCount = count / 2;
|
||||
var shortBuffer = new short[shortCount];
|
||||
java.nio.ByteBuffer.wrap((sbyte[]) (object) buffer)
|
||||
.order(java.nio.ByteOrder.LITTLE_ENDIAN).asShortBuffer()
|
||||
.get(shortBuffer);
|
||||
dataArray = shortBuffer;
|
||||
dataCount = shortCount;
|
||||
}
|
||||
else
|
||||
throw new ArgumentException("bad wBitsPerSample");
|
||||
|
||||
markerFrame = dataCount / nChannels;
|
||||
|
||||
Name = name;
|
||||
Duration = TimeSpan.FromSeconds(count / (double) nAvgBytesPerSec);
|
||||
}
|
||||
|
||||
//
|
||||
// Constructor
|
||||
//
|
||||
|
||||
public SoundEffect(byte[] buffer, int sampleRate, AudioChannels channels)
|
||||
: this(null, buffer, 0, buffer.Length,
|
||||
1 /* WAVE_FORMAT_PCM */, (ushort) channels, (uint) sampleRate,
|
||||
(uint) (sampleRate * ((ushort) channels * 2)),
|
||||
(ushort) ((ushort) channels * 2), 16, 0, 0)
|
||||
{
|
||||
}
|
||||
|
||||
//
|
||||
// Constructor
|
||||
//
|
||||
|
||||
public SoundEffect(byte[] buffer, int offset, int count, int sampleRate,
|
||||
AudioChannels channels, int loopStart, int loopLength)
|
||||
: this(null, buffer, offset, count,
|
||||
1 /* WAVE_FORMAT_PCM */, (ushort) channels, (uint) sampleRate,
|
||||
(uint) (sampleRate * ((ushort) channels * 2)),
|
||||
(ushort) ((ushort) channels * 2), 16, loopStart, loopLength)
|
||||
{
|
||||
}
|
||||
|
||||
//
|
||||
// FromStream
|
||||
//
|
||||
|
||||
public static SoundEffect FromStream(Stream stream)
|
||||
{
|
||||
using (BinaryReader reader = new BinaryReader(stream))
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
if (new string(reader.ReadChars(4)) != "RIFF")
|
||||
break;
|
||||
reader.ReadUInt32(); // skip chunk size
|
||||
if (new string(reader.ReadChars(4)) != "WAVE")
|
||||
break;
|
||||
|
||||
if (new string(reader.ReadChars(4)) != "fmt ")
|
||||
break;
|
||||
if (reader.ReadInt32() != 16) // fmt chunk size always 16
|
||||
break;
|
||||
|
||||
var wFormatTag = reader.ReadUInt16();
|
||||
var nChannels = reader.ReadUInt16();
|
||||
var nSamplesPerSec = reader.ReadUInt32();
|
||||
var nAvgBytesPerSec = reader.ReadUInt32();
|
||||
var nBlockAlign = reader.ReadUInt16();
|
||||
var wBitsPerSample = reader.ReadUInt16();
|
||||
|
||||
if (new string(reader.ReadChars(4)) != "data")
|
||||
break;
|
||||
|
||||
var count = reader.ReadInt32();
|
||||
var buffer = reader.ReadBytes(count);
|
||||
|
||||
return new SoundEffect(null, buffer, 0, count, wFormatTag, nChannels,
|
||||
nSamplesPerSec, nAvgBytesPerSec,
|
||||
nBlockAlign, wBitsPerSample, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
throw new BadImageFormatException("invalid wave data for sound effect");
|
||||
}
|
||||
|
||||
//
|
||||
// Destructor
|
||||
//
|
||||
|
||||
~SoundEffect() => Dispose();
|
||||
|
||||
//
|
||||
// Dispose
|
||||
//
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (! IsDisposed)
|
||||
{
|
||||
IsDisposed = true;
|
||||
DiscardInstance(null, this);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Properties
|
||||
//
|
||||
|
||||
public string Name { get; set; }
|
||||
public TimeSpan Duration { get; private set; }
|
||||
public bool IsDisposed { get; private set; }
|
||||
|
||||
//
|
||||
// Play
|
||||
//
|
||||
|
||||
public bool Play() => Play(1f, 0f, 0f);
|
||||
|
||||
public bool Play(float volume, float pitch, float pan)
|
||||
=> CreateInstance().Play(volume, pitch, pan);
|
||||
|
||||
//
|
||||
// CreateInstance
|
||||
//
|
||||
|
||||
public SoundEffectInstance CreateInstance()
|
||||
{
|
||||
var inst = new SoundEffectInstance(this);
|
||||
try
|
||||
{
|
||||
instancesLock.@lock();
|
||||
instancesList.add(new java.lang.@ref.WeakReference(inst));
|
||||
}
|
||||
finally
|
||||
{
|
||||
instancesLock.unlock();
|
||||
}
|
||||
return inst;
|
||||
}
|
||||
|
||||
//
|
||||
// DiscardInstance
|
||||
//
|
||||
|
||||
public static void DiscardInstance(SoundEffectInstance discardInstance, SoundEffect discardEffect)
|
||||
{
|
||||
if (instancesLock.isHeldByCurrentThread())
|
||||
return;
|
||||
try
|
||||
{
|
||||
instancesLock.@lock();
|
||||
for (int idx = instancesList.size(); idx-- > 0;)
|
||||
{
|
||||
var instRef = (java.lang.@ref.WeakReference) instancesList.get(idx);
|
||||
var inst = (SoundEffectInstance) instRef.get();
|
||||
if (inst == discardInstance || inst == null || inst.ShouldDiscard(discardEffect))
|
||||
instancesList.remove(idx);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
instancesLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// ReleaseInstance
|
||||
//
|
||||
|
||||
public static bool ReleaseInstance()
|
||||
{
|
||||
bool found = false;
|
||||
try
|
||||
{
|
||||
instancesLock.@lock();
|
||||
int num = instancesList.size();
|
||||
for (int idx = 0; idx < num; idx++)
|
||||
{
|
||||
var instRef = (java.lang.@ref.WeakReference) instancesList.get(idx);
|
||||
var inst = (SoundEffectInstance) instRef.get();
|
||||
if (inst != null && inst.ReleaseTrack(false))
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
instancesLock.unlock();
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
//
|
||||
// GetSampleDuration, GetSampleSizeInBytes
|
||||
//
|
||||
|
||||
public static TimeSpan GetSampleDuration(int sizeInBytes, int sampleRate,
|
||||
AudioChannels channels)
|
||||
=> TimeSpan.FromSeconds(
|
||||
((sizeInBytes / (2 * (int) channels)) / (float) sampleRate));
|
||||
|
||||
public static int GetSampleSizeInBytes(TimeSpan duration, int sampleRate,
|
||||
AudioChannels channels)
|
||||
=> (int) (duration.TotalSeconds * sampleRate * 2 * (int) channels);
|
||||
|
||||
//
|
||||
// MasterVolume, DistanceScale, DopplerScale, SpeedOfSound (no-op)
|
||||
//
|
||||
|
||||
public static float MasterVolume { get; set; }
|
||||
public static float DistanceScale { get; set; }
|
||||
public static float DopplerScale { get; set; }
|
||||
public static float SpeedOfSound { get; set; }
|
||||
}
|
||||
|
||||
|
||||
|
||||
//
|
||||
// SoundEffectInstance
|
||||
//
|
||||
|
||||
public class SoundEffectInstance : IDisposable
|
||||
{
|
||||
[java.attr.RetainType] private SoundEffect effect;
|
||||
[java.attr.RetainType] private SoundEffectInstanceWatcher watcher;
|
||||
[java.attr.RetainType] private android.media.AudioTrack track;
|
||||
[java.attr.RetainType] private float pitch, pan, volume;
|
||||
[java.attr.RetainType] private bool isLooped;
|
||||
|
||||
//
|
||||
// Constructor (for SoundEffect.CreateInstance)
|
||||
//
|
||||
|
||||
public SoundEffectInstance(SoundEffect fromEffect)
|
||||
{
|
||||
effect = fromEffect;
|
||||
volume = 1f;
|
||||
}
|
||||
|
||||
//
|
||||
// CreateTrack
|
||||
//
|
||||
|
||||
private void CreateTrack(bool tryRelease)
|
||||
{
|
||||
int numToWrite, numWritten;
|
||||
|
||||
if (effect.dataArray is sbyte[] byteData)
|
||||
{
|
||||
numToWrite = byteData.Length;
|
||||
track = new android.media.AudioTrack(
|
||||
android.media.AudioManager.STREAM_MUSIC,
|
||||
effect.sampleRate, effect.channelConfig,
|
||||
android.media.AudioFormat.ENCODING_PCM_8BIT,
|
||||
numToWrite, android.media.AudioTrack.MODE_STATIC);
|
||||
numWritten = track.write(byteData, 0, numToWrite);
|
||||
}
|
||||
|
||||
else if (effect.dataArray is short[] shortData)
|
||||
{
|
||||
numToWrite = shortData.Length;
|
||||
track = new android.media.AudioTrack(
|
||||
android.media.AudioManager.STREAM_MUSIC,
|
||||
effect.sampleRate, effect.channelConfig,
|
||||
android.media.AudioFormat.ENCODING_PCM_16BIT,
|
||||
numToWrite * 2, android.media.AudioTrack.MODE_STATIC);
|
||||
numWritten = track.write(shortData, 0, numToWrite);
|
||||
}
|
||||
else
|
||||
{
|
||||
numToWrite = 0;
|
||||
numWritten = android.media.AudioTrack.ERROR_INVALID_OPERATION;
|
||||
}
|
||||
|
||||
if (numWritten != numToWrite)
|
||||
{
|
||||
track = null;
|
||||
if (numWritten < 0)
|
||||
{
|
||||
if (SoundEffect.ReleaseInstance())
|
||||
CreateTrack(false);
|
||||
}
|
||||
if (track == null)
|
||||
{
|
||||
GameRunner.Log($"SoundEffectInstance '{effect.Name}' error {numWritten}/{numToWrite}");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
track.setNotificationMarkerPosition(effect.markerFrame);
|
||||
track.setPlaybackPositionUpdateListener(watcher = new SoundEffectInstanceWatcher());
|
||||
}
|
||||
|
||||
//
|
||||
// ReleaseTrack
|
||||
//
|
||||
|
||||
public bool ReleaseTrack(bool disposing)
|
||||
{
|
||||
var track = this.track;
|
||||
if (track != null)
|
||||
{
|
||||
if (disposing || track.getPlayState() == 1 /* android.media.AudioTrack.PLAYSTATE_STOPPED */)
|
||||
{
|
||||
this.track = null;
|
||||
track.setPlaybackPositionUpdateListener(null);
|
||||
track.stop();
|
||||
track.release();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// Destructor
|
||||
//
|
||||
|
||||
~SoundEffectInstance() => Dispose(true);
|
||||
|
||||
//
|
||||
// Dispose
|
||||
//
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (! IsDisposed)
|
||||
{
|
||||
ReleaseTrack(true);
|
||||
SoundEffect.DiscardInstance(this, null);
|
||||
effect = null;
|
||||
IsDisposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() => Dispose(true);
|
||||
|
||||
//
|
||||
// ShouldDiscard
|
||||
//
|
||||
|
||||
public bool ShouldDiscard(SoundEffect fromEffect)
|
||||
{
|
||||
if (effect == fromEffect)
|
||||
Dispose(true);
|
||||
return IsDisposed;
|
||||
}
|
||||
|
||||
//
|
||||
// Properties
|
||||
//
|
||||
|
||||
public SoundState State
|
||||
{
|
||||
get
|
||||
{
|
||||
var track = this.track;
|
||||
if (track == null)
|
||||
return SoundState.Stopped;
|
||||
|
||||
return track.getPlayState() switch
|
||||
{
|
||||
1 /* android.media.AudioTrack.PLAYSTATE_STOPPED */ => SoundState.Stopped,
|
||||
2 /* android.media.AudioTrack.PLAYSTATE_PAUSED */ => SoundState.Paused,
|
||||
3 /* android.media.AudioTrack.PLAYSTATE_PLAYING */ => SoundState.Playing,
|
||||
_ => throw new InvalidOperationException()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsDisposed { get; protected set; }
|
||||
public float Pitch
|
||||
{
|
||||
get => pitch;
|
||||
set => SetPlaybackRate(value, State == SoundState.Playing);
|
||||
}
|
||||
public float Pan
|
||||
{
|
||||
get => pan;
|
||||
set => SetStereoVolume(volume, value, State == SoundState.Playing);
|
||||
}
|
||||
public float Volume
|
||||
{
|
||||
get => volume;
|
||||
set => SetStereoVolume(value, pan, State == SoundState.Playing);
|
||||
}
|
||||
|
||||
//
|
||||
// IsLooped
|
||||
//
|
||||
// note that looping does not respect any custom loop points,
|
||||
// and always occurs on the entire effect
|
||||
//
|
||||
|
||||
public virtual bool IsLooped
|
||||
{
|
||||
get => isLooped;
|
||||
set
|
||||
{
|
||||
if (State == SoundState.Playing)
|
||||
throw new InvalidOperationException();
|
||||
isLooped = value;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// SetPlaybackRate
|
||||
//
|
||||
|
||||
private void SetPlaybackRate(float pitch, bool playing)
|
||||
{
|
||||
if (pitch < -1f)
|
||||
pitch = -1f;
|
||||
else if (pitch > 1f)
|
||||
pitch = 1f;
|
||||
this.pitch = pitch;
|
||||
|
||||
if (playing)
|
||||
{
|
||||
// convert pitch from range 0 .. 1 to range 0.5 .. 2
|
||||
// (which represents half to twice the sample rate)
|
||||
if (pitch < 0f)
|
||||
pitch = 1f + pitch * 0.5f;
|
||||
else if (pitch > 0f)
|
||||
pitch = 1f + pitch;
|
||||
int r = (int) (effect.sampleRate * pitch);
|
||||
var track = this.track;
|
||||
if (track != null)
|
||||
track.setPlaybackRate(r);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// SetStereoVolume
|
||||
//
|
||||
|
||||
private void SetStereoVolume(float volume, float pan, bool playing)
|
||||
{
|
||||
if (volume < 0f)
|
||||
volume = 0f;
|
||||
else if (volume > 1f)
|
||||
volume = 1f;
|
||||
this.volume = volume;
|
||||
|
||||
if (pan < -1f)
|
||||
pan = -1f;
|
||||
else if (pan > 1f)
|
||||
pan = 1f;
|
||||
this.pan = pan;
|
||||
|
||||
if (playing)
|
||||
{
|
||||
float leftGain = 1f;
|
||||
float rightGain = 1f;
|
||||
if (pan < 0f)
|
||||
{
|
||||
leftGain *= 0f - pan;
|
||||
rightGain *= pan + 1f;
|
||||
}
|
||||
else if (pan > 0f)
|
||||
{
|
||||
rightGain *= pan;
|
||||
leftGain *= 1f - pan;
|
||||
}
|
||||
var track = this.track;
|
||||
if (track != null)
|
||||
track.setStereoVolume(leftGain * volume, rightGain * volume);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Play, Pause, Resume, Stop
|
||||
//
|
||||
|
||||
public virtual void Play()
|
||||
{
|
||||
if (State != SoundState.Playing)
|
||||
{
|
||||
var track = this.track;
|
||||
if (track == null)
|
||||
{
|
||||
CreateTrack(true);
|
||||
track = this.track;
|
||||
}
|
||||
|
||||
if (track != null)
|
||||
{
|
||||
SetPlaybackRate(pitch, true);
|
||||
SetStereoVolume(volume, pan, true);
|
||||
|
||||
watcher.instance = this;
|
||||
track.play();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Pause()
|
||||
{
|
||||
var track = this.track;
|
||||
if (track != null && State == SoundState.Playing)
|
||||
track.pause();
|
||||
}
|
||||
|
||||
public void Resume() => Play();
|
||||
|
||||
public void Stop() => Stop(true);
|
||||
|
||||
public void Stop(bool immediate)
|
||||
{
|
||||
if (immediate)
|
||||
{
|
||||
var track = this.track;
|
||||
if (track != null && State != SoundState.Stopped)
|
||||
track.stop();
|
||||
}
|
||||
if (watcher != null)
|
||||
watcher.instance = null;
|
||||
}
|
||||
|
||||
//
|
||||
// Play (for SoundEffect.Play)
|
||||
//
|
||||
|
||||
public bool Play(float volume, float pitch, float pan)
|
||||
{
|
||||
this.volume = volume;
|
||||
this.pitch = pitch;
|
||||
this.pan = pan;
|
||||
Play();
|
||||
if (track == null)
|
||||
{
|
||||
Dispose(true);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//
|
||||
// Apply3D (no-op)
|
||||
//
|
||||
|
||||
public void Apply3D(AudioListener listener, AudioEmitter emitter) { }
|
||||
public void Apply3D(AudioListener[] listeners, AudioEmitter emitter) { }
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
//
|
||||
// SoundEffectInstanceWatcher
|
||||
//
|
||||
|
||||
public class SoundEffectInstanceWatcher :
|
||||
android.media.AudioTrack.OnPlaybackPositionUpdateListener
|
||||
{
|
||||
[java.attr.RetainType] public SoundEffectInstance instance;
|
||||
|
||||
[java.attr.RetainName]
|
||||
public void onMarkerReached(android.media.AudioTrack track)
|
||||
{
|
||||
// release the strong reference to the SoundEffectInstance,
|
||||
// so it can be garbage collected if not otherwise referenced
|
||||
track.stop();
|
||||
var instance = this.instance;
|
||||
if (instance != null)
|
||||
{
|
||||
if (instance.IsLooped && (! instance.IsDisposed))
|
||||
track.play();
|
||||
else
|
||||
this.instance = null;
|
||||
}
|
||||
}
|
||||
|
||||
[java.attr.RetainName]
|
||||
public void onPeriodicNotification(android.media.AudioTrack track) { }
|
||||
}
|
||||
|
||||
}
|
@ -59,4 +59,10 @@ namespace Microsoft.Xna.Framework
|
||||
|
||||
}
|
||||
|
||||
/*internal static class TitleLocation
|
||||
{
|
||||
// there is no file path for asset files which are part of the APK
|
||||
public static string Path => throw new System.PlatformNotSupportedException();
|
||||
}*/
|
||||
|
||||
}
|
||||
|
@ -60,6 +60,7 @@
|
||||
<Reference Include="Microsoft.Xna.Framework.Graphics, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86" />
|
||||
<Reference Include="Microsoft.Xna.Framework.Input.Touch, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=MSIL" />
|
||||
<Reference Include="Microsoft.Xna.Framework.Storage, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=MSIL" />
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
@ -3,6 +3,7 @@ using System;
|
||||
using System.IO;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Microsoft.Xna.Framework.Audio;
|
||||
|
||||
namespace Demo1
|
||||
{
|
||||
@ -14,6 +15,8 @@ namespace Demo1
|
||||
public Texture2D white;
|
||||
private SpriteBatch spriteBatch;
|
||||
private Font myFont;
|
||||
private SoundEffectInstance effect;
|
||||
private bool playEffect;
|
||||
private int pageNumber, pageNumberOld;
|
||||
private bool paused;
|
||||
private bool anyDrawText;
|
||||
@ -58,8 +61,33 @@ namespace Demo1
|
||||
// Texture2D.FromStream(GraphicsDevice, stream);
|
||||
//
|
||||
// to disable processing, open Properties on the image in the
|
||||
// Content project, set "Build Action: None", and
|
||||
// "Copy to Output Directory: Copy if newer".
|
||||
// Content project, and change the following in the Advanced tab:
|
||||
// "Build Action: None" and "Copy to Output Directory: Copy if newer".
|
||||
|
||||
effect = Content.Load<SoundEffect>("effect").CreateInstance();
|
||||
effect.Pitch = 0.2f;
|
||||
|
||||
// the XNA content processor for Song converts music files to WMA
|
||||
// format, which Android does not support playing. use MP3 instead.
|
||||
//
|
||||
// to disable processing, open Properties on the image in the
|
||||
// Content project, and change the following in the Advanced tab:
|
||||
// "Build Action: None" and "Copy to Output Directory: Copy if newer".
|
||||
//
|
||||
// note that XNA on Windows may not play MP3 files which contain ID3
|
||||
// tags. use one of the many free utilities to remove such tags.
|
||||
//
|
||||
// unsupported BNA MediaPlayer: queueing more than one song at a time;
|
||||
// the ActiveSongChanged event; visualization data.
|
||||
|
||||
Microsoft.Xna.Framework.Media.MediaPlayer.Volume = 0.05f;
|
||||
Microsoft.Xna.Framework.Media.MediaPlayer.Play(
|
||||
Microsoft.Xna.Framework.Media.Song.FromUri("Song1",
|
||||
new System.Uri(Content.RootDirectory + "/music.mp3", UriKind.Relative)));
|
||||
Microsoft.Xna.Framework.Media.MediaPlayer.IsRepeating = true;
|
||||
// song duration is populated during the call to Play()
|
||||
// Console.WriteLine(Microsoft.Xna.Framework.Media.MediaPlayer.Queue.ActiveSong.Duration);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -80,12 +108,26 @@ namespace Demo1
|
||||
if (paused)
|
||||
return;
|
||||
|
||||
if (playEffect)
|
||||
{
|
||||
effect.Play();
|
||||
playEffect = false;
|
||||
}
|
||||
|
||||
// basic scene management for the purpose of this demo:
|
||||
// after either arrow at the top of the screen is clicked, and
|
||||
// the current page number has changed, create the new 'page'.
|
||||
|
||||
if (pageNumber != pageNumberOld)
|
||||
if (pageNumber != pageNumberOld)
|
||||
{
|
||||
if (pageComponent != null)
|
||||
{
|
||||
if (effect.State == SoundState.Playing)
|
||||
effect.Stop();
|
||||
effect.Pan = (pageNumber > pageNumberOld) ? 1f : -1f;
|
||||
playEffect = true;
|
||||
}
|
||||
|
||||
const int LAST_PAGE = 4;
|
||||
if (pageNumber <= 0)
|
||||
pageNumber = LAST_PAGE;
|
||||
|
@ -42,7 +42,7 @@ namespace Demo1
|
||||
{
|
||||
if (renderToTexture)
|
||||
{
|
||||
((Game1)Game).DrawFlushBatch();
|
||||
((Game1) Game).DrawFlushBatch();
|
||||
renderTargetWidth = Config.ClientWidth;
|
||||
renderTargetHeight = Config.ClientHeight;
|
||||
if (renderTarget != null)
|
||||
|
@ -48,6 +48,10 @@ namespace Demo1
|
||||
file = container.OpenFile("state-v1.bin", FileMode.OpenOrCreate);
|
||||
if (file.Length > 4)
|
||||
Read(file);
|
||||
|
||||
// reset file in case we crash
|
||||
file.SetLength(0);
|
||||
file.Flush();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
@ -1,4 +1,11 @@
|
||||
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<!--
|
||||
to resolve error in Microsoft.Xna.GameStudio.ContentPipeline.targets line 78,
|
||||
error loading pipeline assembly Microsoft.Build.Framework.dll:
|
||||
cd C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\MSBuild\Current\Bin
|
||||
gacutil /i Microsoft.Build.Framework.dll
|
||||
see also https://github.com/dotnet/msbuild/issues/1831
|
||||
-->
|
||||
<PropertyGroup>
|
||||
<ProjectGuid>{E50B6FE1-C91C-4226-BA4B-75DEFDEF4CA3}</ProjectGuid>
|
||||
<ProjectTypeGuids>{96E2B04D-8817-42c6-938A-82C39BA4D311};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||
@ -64,6 +71,21 @@
|
||||
<Processor>TextureProcessor</Processor>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="effect.mp3">
|
||||
<Name>effect</Name>
|
||||
<Importer>Mp3Importer</Importer>
|
||||
<Processor>SoundEffectProcessor</Processor>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="music.mp3">
|
||||
<Name>music</Name>
|
||||
<Importer>Mp3Importer</Importer>
|
||||
<Processor>SongProcessor</Processor>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildExtensionsPath)\Microsoft\XNA Game Studio\$(XnaFrameworkVersion)\Microsoft.Xna.GameStudio.ContentPipeline.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
|
BIN
Demo1/Demo1Content/effect.mp3
Normal file
BIN
Demo1/Demo1Content/effect.mp3
Normal file
Binary file not shown.
BIN
Demo1/Demo1Content/music.mp3
Normal file
BIN
Demo1/Demo1Content/music.mp3
Normal file
Binary file not shown.
@ -1,5 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<!--
|
||||
to resolve error in Microsoft.PackageDependencyResolution.targets about
|
||||
Assets file 'project.assets.json' doesn't have a target for 'net461'
|
||||
make sure you use the latest nuget.exe, or at least version 5.8.0
|
||||
-->
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>net461</TargetFramework>
|
||||
@ -14,6 +20,9 @@
|
||||
|
||||
<Compile Include="Library1.fs" />
|
||||
|
||||
<PackageReference Include="FSharp.Core" Version="4.7.0" />
|
||||
<PackageReference Update="FSharp.Core" Version="4.7.0" />
|
||||
|
||||
<Reference Include="Microsoft.Xna.Framework">
|
||||
<HintPath>C:\Program Files (x86)\Microsoft XNA\XNA Game Studio\v4.0\References\Windows\x86\Microsoft.Xna.Framework.dll</HintPath>
|
||||
</Reference>
|
||||
|
@ -7,8 +7,10 @@ open Microsoft.Xna.Framework.Graphics
|
||||
type StencilDemo (game : Game) =
|
||||
inherit DrawableGameComponent(game)
|
||||
|
||||
let mutable spriteBatch = null
|
||||
let mutable texture = null
|
||||
let mutable spriteBatch = new SpriteBatch (game.GraphicsDevice)
|
||||
let mutable logoTexture = game.Content.Load("fsharp256")
|
||||
let mutable backTexture = new Texture2D(game.GraphicsDevice, 16, 16)
|
||||
let mutable counter = 0
|
||||
|
||||
// using option here solely to force a dependency on FSharp.Core.dll
|
||||
let mutable rectFunc : (Game -> Rectangle) option = None
|
||||
@ -26,12 +28,36 @@ type StencilDemo (game : Game) =
|
||||
(float32) (Math.Cos(gameTime.TotalGameTime.TotalMilliseconds * 0.001)))
|
||||
|
||||
override Game.Initialize() =
|
||||
spriteBatch <- new SpriteBatch (game.GraphicsDevice)
|
||||
texture <- game.Content.Load("fsharp256")
|
||||
rectFunc <- Some logoRect
|
||||
colorFunc <- Some logoColor
|
||||
|
||||
override Game.Draw gameTime =
|
||||
|
||||
let backArray = Array.zeroCreate (16 * 16)
|
||||
backTexture.GetData backArray
|
||||
|
||||
if counter = 0
|
||||
then
|
||||
backArray.[12 * 16] <- 0xFF0000FF
|
||||
counter <- 1
|
||||
else
|
||||
for y = 4 to 11 do
|
||||
for x = 0 to 15 do
|
||||
let idx = y * 16 + x
|
||||
backArray.[idx] <- backArray.[idx + 1]
|
||||
backArray.[idx + 1] <- 0
|
||||
counter <- match counter with
|
||||
| 60 -> 0
|
||||
| n -> n + 1
|
||||
backTexture.SetData backArray
|
||||
|
||||
spriteBatch.Begin (SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, DepthStencilState.None, new RasterizerState())
|
||||
spriteBatch.Draw (backTexture,
|
||||
Rectangle(0, 0, game.Window.ClientBounds.Width, game.Window.ClientBounds.Height),
|
||||
Nullable (Rectangle(0, 0, 16, 16)),
|
||||
Color.White)
|
||||
spriteBatch.End ()
|
||||
|
||||
spriteBatch.Begin ()
|
||||
spriteBatch.Draw (texture, rectFunc.Value game, colorFunc.Value gameTime)
|
||||
spriteBatch.Draw (logoTexture, rectFunc.Value game, colorFunc.Value gameTime)
|
||||
spriteBatch.End ()
|
@ -17,6 +17,8 @@
|
||||
CONTENT_DIR = path to Content directory
|
||||
e.g. $(OutputDir)/MyGame/Debug/Content
|
||||
|
||||
ICON_PNG = path to a PNG file to use as the app icon
|
||||
|
||||
APK_OUTPUT = path to copy the final APK
|
||||
e.g. $(OutputDir)/MyGame.apk
|
||||
|
||||
|
@ -30,6 +30,7 @@ echo Building BNA. Command:
|
||||
echo MSBuild BNA -p:Configuration=Release
|
||||
echo ========================================
|
||||
MSBuild BNA -p:Configuration=Release
|
||||
if errorlevel 1 goto :EOF
|
||||
pause
|
||||
|
||||
echo ========================================
|
||||
@ -38,14 +39,17 @@ echo nuget restore Demo1
|
||||
echo msbuild Demo1 -p:Configuration=Release -p:Platform="x86"
|
||||
echo ========================================
|
||||
nuget restore Demo1
|
||||
if errorlevel 1 goto :EOF
|
||||
msbuild Demo1 -p:Configuration=Release -p:Platform="x86"
|
||||
if errorlevel 1 goto :EOF
|
||||
pause
|
||||
|
||||
echo ========================================
|
||||
echo Converting Demo1 to APK. Command:
|
||||
echo MSBuild MakeAPK.project -p:INPUT_DLL=.obj\Demo1\Release\Demo1.exe -p:INPUT_DLL_2=.obj\Demo1\Release\Demo1FSharp.dll -p:INPUT_DLL_3=.obj\Demo1\Release\FSharp.Core.dll -p:CONTENT_DIR=.obj\Demo1\Release\Content -p:ICON_PNG=Demo1\Demo1\GameThumbnail.png -p:ANDROID_MANIFEST=Demo1\AndroidManifest.xml -p:KEYSTORE_FILE=.\my.keystore -p:KEYSTORE_PWD=123456 -p:APK_OUTPUT=.obj\Demo1.apk -p:APK_TEMP_DIR=.obj\Demo1\Release\TempApk -p:EXTRA_JAR_1=.obj\BNA.jar -p:EXTRA_JAR_2=%BLUEBONNET_LIB%
|
||||
echo ========================================
|
||||
MSBuild MakeAPK.project -p:INPUT_DLL=.obj\Demo1\Release\Demo1.exe -p:INPUT_DLL_2=.obj\Demo1\Release\Demo1FSharp.dll -p:INPUT_DLL_3=.obj\Demo1\Release\FSharp.Core.dll -p:CONTENT_DIR=.obj\Demo1\Release\Content -p:ICON_PNG=Demo1\Demo1\GameThumbnail.png -p:ANDROID_MANIFEST=Demo1\AndroidManifest.xml -p:KEYSTORE_FILE=.\my.keystore -p:KEYSTORE_PWD=123456 -p:APK_OUTPUT=.obj\Demo1.apk -p:APK_TEMP_DIR=.obj\Demo1\Release\TempApk -p:EXTRA_JAR_1=.obj\BNA.jar -p:EXTRA_JAR_2=%BLUEBONNET_LIB%
|
||||
MSBuild MakeAPK.project -p:INPUT_DLL=.obj\Demo1\Release\Demo1.exe -p:INPUT_DLL_2=.obj\Demo1\Release\Demo1FSharp.dll -p:INPUT_DLL_3=.obj\Demo1\Release\FSharp.Core.dll -p:CONTENT_DIR=.obj\Demo1\Release\Content -p:ICON_PNG=Demo1\Demo1\GameThumbnail.png -p:ANDROID_MANIFEST=Demo1\AndroidManifest.xml -p:KEYSTORE_FILE=.\my.keystore -p:KEYSTORE_PWD=123456 -p:APK_OUTPUT=.obj\Demo1.apk -p:APK_TEMP_DIR=.obj\Demo1\Release\TempApk -p:EXTRA_JAR_1=.obj\BNA.jar -p:EXTRA_JAR_2=%BLUEBONNET_LIB%
|
||||
if errorlevel 1 goto :EOF
|
||||
|
||||
echo ========================================
|
||||
echo All done
|
||||
|
Loading…
x
Reference in New Issue
Block a user