mirror of
https://github.com/clangen/musikcube.git
synced 2024-10-02 13:02:35 +00:00
Fixed WaveOut to actually use both channels, instead of just the left one. Also fixed a few bugs related to closing WaveOut.
This commit is contained in:
parent
fe1b2827e4
commit
a0f7a7670c
@ -1,233 +0,0 @@
|
|||||||
//////////////////////////////////////////////////////////////////////////////
|
|
||||||
// Copyright © 2007, mC2 team
|
|
||||||
//
|
|
||||||
// All rights reserved.
|
|
||||||
//
|
|
||||||
// Redistribution and use in source and binary forms, with or without
|
|
||||||
// modification, are permitted provided that the following conditions are met:
|
|
||||||
//
|
|
||||||
// * Redistributions of source code must retain the above copyright notice,
|
|
||||||
// this list of conditions and the following disclaimer.
|
|
||||||
//
|
|
||||||
// * Redistributions in binary form must reproduce the above copyright
|
|
||||||
// notice, this list of conditions and the following disclaimer in the
|
|
||||||
// documentation and/or other materials provided with the distribution.
|
|
||||||
//
|
|
||||||
// * Neither the name of the author nor the names of other contributors may
|
|
||||||
// be used to endorse or promote products derived from this software
|
|
||||||
// without specific prior written permission.
|
|
||||||
//
|
|
||||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
||||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
||||||
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
||||||
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
||||||
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
||||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
||||||
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
||||||
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
||||||
// POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
//
|
|
||||||
//////////////////////////////////////////////////////////////////////////////
|
|
||||||
#include "StdAfx.h"
|
|
||||||
#include "OGGDecoder.h"
|
|
||||||
|
|
||||||
OGGDecoder::OGGDecoder()
|
|
||||||
{
|
|
||||||
// Set the callbacks to handle all file io
|
|
||||||
this->oggCallbacks.read_func = &OggRead;
|
|
||||||
this->oggCallbacks.seek_func = &OggSeek;
|
|
||||||
this->oggCallbacks.tell_func = &OggTell;
|
|
||||||
this->oggCallbacks.close_func = &OggClose;
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t OGGDecoder::OggRead(void *buffer, size_t nofParts, size_t partSize, void *datasource){
|
|
||||||
return (size_t)((OGGDecoder*)datasource)->fileStream->Read(buffer,(long)(nofParts*partSize));
|
|
||||||
}
|
|
||||||
|
|
||||||
int OGGDecoder::OggSeek(void *datasource, ogg_int64_t offset, int whence){
|
|
||||||
switch(whence){
|
|
||||||
case SEEK_CUR:
|
|
||||||
{
|
|
||||||
long currentPosition = ((OGGDecoder*)datasource)->fileStream->Position();
|
|
||||||
if( ((OGGDecoder*)datasource)->fileStream->SetPosition(currentPosition+(long)offset)){
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case SEEK_END:
|
|
||||||
{
|
|
||||||
long fileSize = ((OGGDecoder*)datasource)->fileStream->Filesize();
|
|
||||||
if( ((OGGDecoder*)datasource)->fileStream->SetPosition(fileSize)){
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
if( ((OGGDecoder*)datasource)->fileStream->SetPosition((long)offset)){
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unsuccessfull
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
long OGGDecoder::OggTell(void *datasource){
|
|
||||||
return ((OGGDecoder*)datasource)->fileStream->Position();
|
|
||||||
}
|
|
||||||
|
|
||||||
int OGGDecoder::OggClose(void *datasource){
|
|
||||||
if( ((OGGDecoder*)datasource)->fileStream->Close() ){
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
OGGDecoder::~OGGDecoder()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OGGDecoder::Open(musik::core::filestreams::IFileStream *fileStream)
|
|
||||||
{
|
|
||||||
this->fileStream = fileStream;
|
|
||||||
|
|
||||||
if(ov_open_callbacks(this,&this->oggFile,NULL,0,this->oggCallbacks)!=0){
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void OGGDecoder::Destroy(void){
|
|
||||||
|
|
||||||
ov_clear(&this->oggFile);
|
|
||||||
|
|
||||||
delete this;
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
bool OGGDecoder::GetFormat(unsigned long * SampleRate, unsigned long * Channels){
|
|
||||||
vorbis_info *info = ov_info(&this->oggFile,-1);
|
|
||||||
if(info){
|
|
||||||
*SampleRate = info->rate;
|
|
||||||
*Channels = info->channels;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OGGDecoder::GetLength(unsigned long * MS){
|
|
||||||
double time = ov_time_total(&this->oggFile, 0);
|
|
||||||
if( time!=OV_EINVAL ){
|
|
||||||
*MS = (unsigned long)(time * 1000.0);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
double OGGDecoder::SetPosition(double second,double totalLength){
|
|
||||||
|
|
||||||
if(ov_seekable(&this->oggFile)){
|
|
||||||
if(!ov_time_seek(&this->oggFile, second)){
|
|
||||||
return ov_time_tell(&this->oggFile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OGGDecoder::GetBuffer(IBuffer *buffer){
|
|
||||||
vorbis_info *info = ov_info(&this->oggFile, -1);
|
|
||||||
|
|
||||||
long nofSamplesMax = 1024*2;
|
|
||||||
int currentSelection;
|
|
||||||
|
|
||||||
buffer->SetChannels(info->channels);
|
|
||||||
buffer->SetSampleRate(info->rate);
|
|
||||||
buffer->SetSamples(nofSamplesMax);
|
|
||||||
|
|
||||||
float ** pcm;
|
|
||||||
unsigned long samplesRead = ov_read_float(&this->oggFile, &pcm, nofSamplesMax, ¤tSelection);
|
|
||||||
if(samplesRead == 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer->SetSamples(samplesRead);
|
|
||||||
|
|
||||||
/* MUSIKCUBE EXPECTS
|
|
||||||
SPEAKER_FRONT_LEFT 0x1
|
|
||||||
SPEAKER_FRONT_RIGHT 0x2
|
|
||||||
SPEAKER_FRONT_CENTER 0x4
|
|
||||||
SPEAKER_LOW_FREQUENCY 0x8
|
|
||||||
SPEAKER_BACK_LEFT 0x10
|
|
||||||
SPEAKER_BACK_RIGHT 0x20
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* OGG DOES
|
|
||||||
|
|
||||||
One channel: the stream is monophonic
|
|
||||||
Two channels: the stream is stereo. channel order: left, right.
|
|
||||||
Three channels: the stream is a 1d-surround encoding. channel order: left, center, right
|
|
||||||
Four channels: the stream is quadraphonic surround. channel order: front left, front right, rear left, rear right
|
|
||||||
Five channels: the stream is five-channel surround. channel order: front left, front center, front right, rear left, rear right
|
|
||||||
Six channels: (used in Dolby Digital/AC3) the stream is 5,1 surround. channel order: front left, front center, front right, rear left, rear right, LFE (the sixth channel is entirely bass).
|
|
||||||
*/
|
|
||||||
|
|
||||||
// so we need to REORDER
|
|
||||||
float *pDataBuffer = buffer->BufferPointer();
|
|
||||||
|
|
||||||
if(info->channels == 3){
|
|
||||||
float *pDataBuffer = buffer->BufferPointer();
|
|
||||||
for(unsigned long x=0; x<samplesRead; ++x){
|
|
||||||
*pDataBuffer = pcm[0][x];
|
|
||||||
++pDataBuffer;
|
|
||||||
*pDataBuffer = pcm[2][x];
|
|
||||||
++pDataBuffer;
|
|
||||||
*pDataBuffer = pcm[1][x];
|
|
||||||
++pDataBuffer;
|
|
||||||
}
|
|
||||||
}else if(info->channels == 5){
|
|
||||||
for(unsigned long x=0; x<samplesRead; ++x){
|
|
||||||
*pDataBuffer = pcm[0][x];
|
|
||||||
++pDataBuffer;
|
|
||||||
*pDataBuffer = pcm[2][x];
|
|
||||||
++pDataBuffer;
|
|
||||||
*pDataBuffer = pcm[1][x];
|
|
||||||
++pDataBuffer;
|
|
||||||
*pDataBuffer = pcm[3][x];
|
|
||||||
++pDataBuffer;
|
|
||||||
*pDataBuffer = pcm[4][x];
|
|
||||||
++pDataBuffer;
|
|
||||||
}
|
|
||||||
}else if(info->channels == 6){
|
|
||||||
for(unsigned long x=0; x<samplesRead; ++x){
|
|
||||||
*pDataBuffer = pcm[0][x];
|
|
||||||
++pDataBuffer;
|
|
||||||
*pDataBuffer = pcm[2][x];
|
|
||||||
++pDataBuffer;
|
|
||||||
*pDataBuffer = pcm[1][x];
|
|
||||||
++pDataBuffer;
|
|
||||||
*pDataBuffer = pcm[5][x];
|
|
||||||
++pDataBuffer;
|
|
||||||
*pDataBuffer = pcm[3][x];
|
|
||||||
++pDataBuffer;
|
|
||||||
*pDataBuffer = pcm[4][x];
|
|
||||||
++pDataBuffer;
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
for(unsigned long x=0; x<samplesRead; ++x){
|
|
||||||
for(int channel(0); channel<info->channels; ++channel){
|
|
||||||
*pDataBuffer = pcm[channel][x];
|
|
||||||
++pDataBuffer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
@ -32,47 +32,41 @@
|
|||||||
//////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
#include "stdafx.h"
|
#include "stdafx.h"
|
||||||
|
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
|
#include "OggDecoderFactory.h"
|
||||||
|
#include "VorbisDecoder.h"
|
||||||
|
|
||||||
#include "OggSourceSupplier.h"
|
OggDecoderFactory::OggDecoderFactory() {
|
||||||
|
|
||||||
#include "OggDecoder.h"
|
|
||||||
|
|
||||||
OggSourceSupplier::OggSourceSupplier()
|
|
||||||
{
|
|
||||||
}
|
}
|
||||||
|
|
||||||
OggSourceSupplier::~OggSourceSupplier()
|
OggDecoderFactory::~OggDecoderFactory() {
|
||||||
{
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void OggSourceSupplier::Destroy()
|
void OggDecoderFactory::Destroy() {
|
||||||
{
|
|
||||||
delete this;
|
delete this;
|
||||||
}
|
}
|
||||||
|
|
||||||
IDecoder* OggSourceSupplier::CreateDecoder()
|
IDecoder* OggDecoderFactory::CreateDecoder() {
|
||||||
{
|
return new VorbisDecoder();
|
||||||
return new OGGDecoder();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OggSourceSupplier::CanHandle(const utfchar* type) const
|
bool OggDecoderFactory::CanHandle(const utfchar* type) const {
|
||||||
{
|
if (type) {
|
||||||
if(type){
|
|
||||||
utfstring typeString(type);
|
utfstring typeString(type);
|
||||||
if(typeString.find(UTF("ogg"))!=utfstring::npos){
|
|
||||||
|
if (typeString.find(UTF("ogg")) != utfstring::npos) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if(typeString.find(UTF("oga"))!=utfstring::npos){
|
if (typeString.find(UTF("oga")) != utfstring::npos) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if(typeString.find(UTF("audio/ogg"))!=utfstring::npos){
|
if (typeString.find(UTF("audio/ogg")) != utfstring::npos) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if(typeString.find(UTF("audio/vorbis"))!=utfstring::npos){
|
if (typeString.find(UTF("audio/vorbis")) != utfstring::npos) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
@ -37,10 +37,10 @@
|
|||||||
|
|
||||||
using namespace musik::core::audio;
|
using namespace musik::core::audio;
|
||||||
|
|
||||||
class OggSourceSupplier : public IDecoderFactory {
|
class OggDecoderFactory : public IDecoderFactory {
|
||||||
public:
|
public:
|
||||||
OggSourceSupplier();
|
OggDecoderFactory();
|
||||||
~OggSourceSupplier();
|
~OggDecoderFactory();
|
||||||
|
|
||||||
IDecoder* CreateDecoder();
|
IDecoder* CreateDecoder();
|
||||||
void Destroy();
|
void Destroy();
|
214
src/contrib/oggdecoder/VorbisDecoder.cpp
Normal file
214
src/contrib/oggdecoder/VorbisDecoder.cpp
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright © 2007, mC2 team
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer.
|
||||||
|
//
|
||||||
|
// * Redistributions in binary form must reproduce the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer in the
|
||||||
|
// documentation and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// * Neither the name of the author nor the names of other contributors may
|
||||||
|
// be used to endorse or promote products derived from this software
|
||||||
|
// without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
// POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
//
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#include "StdAfx.h"
|
||||||
|
#include "VorbisDecoder.h"
|
||||||
|
|
||||||
|
VorbisDecoder::VorbisDecoder() {
|
||||||
|
this->oggCallbacks.read_func = &OggRead;
|
||||||
|
this->oggCallbacks.seek_func = &OggSeek;
|
||||||
|
this->oggCallbacks.tell_func = &OggTell;
|
||||||
|
this->oggCallbacks.close_func = &OggClose;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t VorbisDecoder::OggRead(void *buffer, size_t nofParts, size_t partSize, void *datasource) {
|
||||||
|
return (size_t)((VorbisDecoder*)datasource)->fileStream->Read(buffer,(long)(nofParts*partSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
int VorbisDecoder::OggSeek(void *datasource, ogg_int64_t offset, int whence) {
|
||||||
|
switch(whence) {
|
||||||
|
case SEEK_CUR:
|
||||||
|
{
|
||||||
|
long currentPosition = ((VorbisDecoder*)datasource)->fileStream->Position();
|
||||||
|
if(((VorbisDecoder*)datasource)->fileStream->SetPosition(currentPosition+(long) offset)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SEEK_END:
|
||||||
|
{
|
||||||
|
long fileSize = ((VorbisDecoder*) datasource)->fileStream->Filesize();
|
||||||
|
if(((VorbisDecoder*) datasource)->fileStream->SetPosition(fileSize)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
if(((VorbisDecoder*) datasource)->fileStream->SetPosition((long) offset)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
long VorbisDecoder::OggTell(void *datasource) {
|
||||||
|
return ((VorbisDecoder*)datasource)->fileStream->Position();
|
||||||
|
}
|
||||||
|
|
||||||
|
int VorbisDecoder::OggClose(void *datasource) {
|
||||||
|
if(((VorbisDecoder*)datasource)->fileStream->Close()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
VorbisDecoder::~VorbisDecoder() {
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VorbisDecoder::Open(musik::core::filestreams::IFileStream *fileStream) {
|
||||||
|
this->fileStream = fileStream;
|
||||||
|
|
||||||
|
if (ov_open_callbacks(this, &this->oggFile, NULL, 0, this->oggCallbacks) != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VorbisDecoder::Destroy() {
|
||||||
|
ov_clear(&this->oggFile);
|
||||||
|
delete this;
|
||||||
|
}
|
||||||
|
|
||||||
|
double VorbisDecoder::SetPosition(double second, double totalLength) {
|
||||||
|
if (ov_seekable(&this->oggFile)) {
|
||||||
|
if (!ov_time_seek(&this->oggFile, second)) {
|
||||||
|
return ov_time_tell(&this->oggFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define OGG_MAX_SAMPLES 1024
|
||||||
|
|
||||||
|
bool VorbisDecoder::GetBuffer(IBuffer *buffer) {
|
||||||
|
int bitstream;
|
||||||
|
float **pcm;
|
||||||
|
|
||||||
|
unsigned long samplesRead = ov_read_float(&this->oggFile, &pcm, OGG_MAX_SAMPLES, &bitstream);
|
||||||
|
|
||||||
|
if (samplesRead == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
vorbis_info *info = ov_info(&this->oggFile, -1);
|
||||||
|
buffer->SetChannels(info->channels);
|
||||||
|
buffer->SetSampleRate(info->rate);
|
||||||
|
buffer->SetSamples(samplesRead);
|
||||||
|
|
||||||
|
/*
|
||||||
|
The musik audio engine expects:
|
||||||
|
|
||||||
|
SPEAKER_FRONT_LEFT 0x1
|
||||||
|
SPEAKER_FRONT_RIGHT 0x2
|
||||||
|
SPEAKER_FRONT_CENTER 0x4
|
||||||
|
SPEAKER_LOW_FREQUENCY 0x8
|
||||||
|
SPEAKER_BACK_LEFT 0x10
|
||||||
|
SPEAKER_BACK_RIGHT 0x20
|
||||||
|
|
||||||
|
OGG returns:
|
||||||
|
|
||||||
|
One channel: the stream is monophonic
|
||||||
|
Two channels: the stream is stereo. channel order: left, right.
|
||||||
|
Three channels: the stream is a 1d-surround encoding. channel order: left, center, right
|
||||||
|
Four channels: the stream is quadraphonic surround. channel order: front left, front right, rear left, rear right
|
||||||
|
Five channels: the stream is five-channel surround. channel order: front left, front center, front right, rear left, rear right
|
||||||
|
Six channels: (used in Dolby Digital/AC3) the stream is 5,1 surround. channel order: front left, front center, front right, rear left, rear right, LFE (the sixth channel is entirely bass).
|
||||||
|
|
||||||
|
... so let's re-order when writing to our output buffer ...
|
||||||
|
*/
|
||||||
|
|
||||||
|
float *pDataBuffer = buffer->BufferPointer();
|
||||||
|
|
||||||
|
if (info->channels == 2) {
|
||||||
|
for (unsigned long x = 0; x < samplesRead; ++x) {
|
||||||
|
pDataBuffer[0] = pcm[0][x];
|
||||||
|
pDataBuffer[1] = pcm[1][x];
|
||||||
|
pDataBuffer += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (info->channels == 3) {
|
||||||
|
for (unsigned long x=0; x < samplesRead; ++x) {
|
||||||
|
*pDataBuffer = pcm[0][x];
|
||||||
|
++pDataBuffer;
|
||||||
|
*pDataBuffer = pcm[2][x];
|
||||||
|
++pDataBuffer;
|
||||||
|
*pDataBuffer = pcm[1][x];
|
||||||
|
++pDataBuffer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (info->channels == 5) {
|
||||||
|
for (unsigned long x = 0; x < samplesRead; ++x) {
|
||||||
|
*pDataBuffer = pcm[0][x];
|
||||||
|
++pDataBuffer;
|
||||||
|
*pDataBuffer = pcm[2][x];
|
||||||
|
++pDataBuffer;
|
||||||
|
*pDataBuffer = pcm[1][x];
|
||||||
|
++pDataBuffer;
|
||||||
|
*pDataBuffer = pcm[3][x];
|
||||||
|
++pDataBuffer;
|
||||||
|
*pDataBuffer = pcm[4][x];
|
||||||
|
++pDataBuffer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (info->channels == 6) {
|
||||||
|
for (unsigned long x = 0; x < samplesRead; ++x) {
|
||||||
|
*pDataBuffer = pcm[0][x];
|
||||||
|
++pDataBuffer;
|
||||||
|
*pDataBuffer = pcm[2][x];
|
||||||
|
++pDataBuffer;
|
||||||
|
*pDataBuffer = pcm[1][x];
|
||||||
|
++pDataBuffer;
|
||||||
|
*pDataBuffer = pcm[5][x];
|
||||||
|
++pDataBuffer;
|
||||||
|
*pDataBuffer = pcm[3][x];
|
||||||
|
++pDataBuffer;
|
||||||
|
*pDataBuffer = pcm[4][x];
|
||||||
|
++pDataBuffer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for (unsigned long x = 0; x < samplesRead; ++x) {
|
||||||
|
for (int channel(0); channel < info->channels; ++channel) {
|
||||||
|
*pDataBuffer = pcm[channel][x];
|
||||||
|
++pDataBuffer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
@ -39,29 +39,28 @@
|
|||||||
|
|
||||||
using namespace musik::core::audio;
|
using namespace musik::core::audio;
|
||||||
|
|
||||||
class OGGDecoder : public IDecoder
|
class VorbisDecoder : public IDecoder
|
||||||
{
|
{
|
||||||
|
|
||||||
public:
|
public:
|
||||||
OGGDecoder();
|
VorbisDecoder();
|
||||||
~OGGDecoder();
|
~VorbisDecoder();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
virtual void Destroy();
|
virtual void Destroy();
|
||||||
virtual double SetPosition(double second,double totalLength);
|
virtual double SetPosition(double second, double totalLength);
|
||||||
virtual bool GetBuffer(IBuffer *buffer);
|
virtual bool GetBuffer(IBuffer *buffer);
|
||||||
virtual bool Open(musik::core::filestreams::IFileStream *fileStream);
|
virtual bool Open(musik::core::filestreams::IFileStream *fileStream);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// OGG callbacks
|
/* libvorbis callbacks */
|
||||||
static size_t OggRead(void *buffer, size_t nofParts, size_t partSize, void *datasource);
|
static size_t OggRead(void *buffer, size_t nofParts, size_t partSize, void *datasource);
|
||||||
static int OggSeek(void *datasource, ogg_int64_t offset, int whence);
|
static int OggSeek(void *datasource, ogg_int64_t offset, int whence);
|
||||||
static long OggTell(void *datasource);
|
static long OggTell(void *datasource);
|
||||||
static int OggClose(void *datasource);
|
static int OggClose(void *datasource);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
musik::core::filestreams::IFileStream *fileStream;
|
musik::core::filestreams::IFileStream *fileStream;
|
||||||
OggVorbis_File oggFile;
|
OggVorbis_File oggFile;
|
||||||
ov_callbacks oggCallbacks;
|
ov_callbacks oggCallbacks;
|
||||||
|
|
||||||
};
|
};
|
@ -93,14 +93,14 @@
|
|||||||
</Link>
|
</Link>
|
||||||
</ItemDefinitionGroup>
|
</ItemDefinitionGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="OGGDecoder.cpp" />
|
<ClCompile Include="VorbisDecoder.cpp" />
|
||||||
<ClCompile Include="oggdecoder_plugin.cpp" />
|
<ClCompile Include="oggdecoder_plugin.cpp" />
|
||||||
<ClCompile Include="OggSourceSupplier.cpp" />
|
<ClCompile Include="OggDecoderFactory.cpp" />
|
||||||
<ClCompile Include="stdafx.cpp" />
|
<ClCompile Include="stdafx.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="OGGDecoder.h" />
|
<ClInclude Include="VorbisDecoder.h" />
|
||||||
<ClInclude Include="OggSourceSupplier.h" />
|
<ClInclude Include="OggDecoderFactory.h" />
|
||||||
<ClInclude Include="stdafx.h" />
|
<ClInclude Include="stdafx.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||||
|
@ -38,7 +38,7 @@
|
|||||||
|
|
||||||
#include <core/sdk/IPlugin.h>
|
#include <core/sdk/IPlugin.h>
|
||||||
|
|
||||||
#include "OggSourceSupplier.h"
|
#include "OggDecoderFactory.h"
|
||||||
|
|
||||||
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
|
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
|
||||||
return true;
|
return true;
|
||||||
@ -57,5 +57,5 @@ extern "C" __declspec(dllexport) musik::core::IPlugin* GetPlugin() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extern "C" __declspec(dllexport) IDecoderFactory* GetDecoderFactory() {
|
extern "C" __declspec(dllexport) IDecoderFactory* GetDecoderFactory() {
|
||||||
return new OggSourceSupplier();
|
return new OggDecoderFactory();
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,7 @@
|
|||||||
|
|
||||||
#include "WaveOut.h"
|
#include "WaveOut.h"
|
||||||
|
|
||||||
#define MAX_VOLUME 65535.0
|
#define MAX_VOLUME 0xFFFF
|
||||||
|
|
||||||
WaveOut::WaveOut()
|
WaveOut::WaveOut()
|
||||||
: waveHandle(NULL)
|
: waveHandle(NULL)
|
||||||
@ -51,7 +51,7 @@ WaveOut::~WaveOut(){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void WaveOut::Destroy(){
|
void WaveOut::Destroy() {
|
||||||
delete this;
|
delete this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,7 +66,8 @@ void WaveOut::Resume() {
|
|||||||
void WaveOut::SetVolume(double volume) {
|
void WaveOut::SetVolume(double volume) {
|
||||||
if (this->waveHandle) {
|
if (this->waveHandle) {
|
||||||
DWORD newVolume = (DWORD)(volume * MAX_VOLUME);
|
DWORD newVolume = (DWORD)(volume * MAX_VOLUME);
|
||||||
waveOutSetVolume(this->waveHandle,newVolume);
|
DWORD leftAndRight = (newVolume << 16) | newVolume;
|
||||||
|
waveOutSetVolume(this->waveHandle, leftAndRight);
|
||||||
}
|
}
|
||||||
|
|
||||||
this->currentVolume = volume;
|
this->currentVolume = volume;
|
||||||
@ -148,7 +149,7 @@ void WaveOut::SetFormat(IBuffer *buffer) {
|
|||||||
this->currentSampleRate = buffer->SampleRate();
|
this->currentSampleRate = buffer->SampleRate();
|
||||||
|
|
||||||
/* format changed, kill the old output device */
|
/* format changed, kill the old output device */
|
||||||
if(this->waveHandle!=NULL) {
|
if (this->waveHandle != NULL) {
|
||||||
waveOutClose(this->waveHandle);
|
waveOutClose(this->waveHandle);
|
||||||
this->waveHandle = NULL;
|
this->waveHandle = NULL;
|
||||||
}
|
}
|
||||||
@ -156,44 +157,41 @@ void WaveOut::SetFormat(IBuffer *buffer) {
|
|||||||
/* reset, and configure speaker output */
|
/* reset, and configure speaker output */
|
||||||
ZeroMemory(&this->waveFormat, sizeof(this->waveFormat));
|
ZeroMemory(&this->waveFormat, sizeof(this->waveFormat));
|
||||||
|
|
||||||
DWORD speakerconfig = 0;
|
DWORD speakerConfig = 0;
|
||||||
|
|
||||||
switch (buffer->Channels()) {
|
switch (buffer->Channels()) {
|
||||||
case 1:
|
case 1:
|
||||||
speakerconfig = KSAUDIO_SPEAKER_MONO;
|
speakerConfig = KSAUDIO_SPEAKER_MONO;
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
speakerconfig = KSAUDIO_SPEAKER_STEREO;
|
speakerConfig = KSAUDIO_SPEAKER_STEREO;
|
||||||
break;
|
break;
|
||||||
case 4:
|
case 4:
|
||||||
speakerconfig = KSAUDIO_SPEAKER_QUAD;
|
speakerConfig = KSAUDIO_SPEAKER_QUAD;
|
||||||
break;
|
break;
|
||||||
case 5:
|
case 5:
|
||||||
speakerconfig = (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT);
|
speakerConfig = (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT);
|
||||||
break;
|
break;
|
||||||
case 6:
|
case 6:
|
||||||
speakerconfig = KSAUDIO_SPEAKER_5POINT1;
|
speakerConfig = KSAUDIO_SPEAKER_5POINT1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
this->waveFormat.Format.cbSize = 22;
|
this->waveFormat.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
|
||||||
this->waveFormat.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
|
this->waveFormat.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
|
||||||
this->waveFormat.Format.nChannels = (WORD) buffer->Channels();
|
this->waveFormat.Format.nChannels = (WORD) buffer->Channels();
|
||||||
|
this->waveFormat.Format.wBitsPerSample = sizeof(float) * 8;
|
||||||
this->waveFormat.Format.nSamplesPerSec = (DWORD) buffer->SampleRate();
|
this->waveFormat.Format.nSamplesPerSec = (DWORD) buffer->SampleRate();
|
||||||
this->waveFormat.Format.wBitsPerSample = 32;
|
|
||||||
|
|
||||||
this->waveFormat.Format.nBlockAlign =
|
int bytesPerSample = this->waveFormat.Format.wBitsPerSample / 8;
|
||||||
(this->waveFormat.Format.wBitsPerSample / 8) * this->waveFormat.Format.nChannels;
|
this->waveFormat.Format.nBlockAlign = bytesPerSample * this->waveFormat.Format.nChannels;
|
||||||
|
|
||||||
this->waveFormat.Format.nAvgBytesPerSec =
|
this->waveFormat.Format.nAvgBytesPerSec =
|
||||||
((this->waveFormat.Format.wBitsPerSample/8) *
|
this->waveFormat.Format.nBlockAlign * this->waveFormat.Format.nSamplesPerSec;
|
||||||
this->waveFormat.Format.nChannels) *
|
|
||||||
this->waveFormat.Format.nSamplesPerSec; /* Compute using nBlkAlign * nSamp/Sec */
|
|
||||||
|
|
||||||
/* IMPORTANT NOTE: wValidBitsPerSample/wReserved/wSamplesPerBlock are a union,
|
/* NOTE: wValidBitsPerSample/wReserved/wSamplesPerBlock are a union */
|
||||||
so don't set wReserved or wSamplesPerBlock to 0 after assigning wValidBitsPerSample. */
|
this->waveFormat.Samples.wValidBitsPerSample = this->waveFormat.Format.wBitsPerSample;
|
||||||
this->waveFormat.Samples.wValidBitsPerSample = 32;
|
this->waveFormat.dwChannelMask = speakerConfig;
|
||||||
this->waveFormat.dwChannelMask = speakerconfig;
|
|
||||||
this->waveFormat.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
|
this->waveFormat.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
|
||||||
|
|
||||||
/* create the output device */
|
/* create the output device */
|
||||||
|
@ -34,6 +34,8 @@
|
|||||||
#include "WaveOutBuffer.h"
|
#include "WaveOutBuffer.h"
|
||||||
#include "WaveOut.h"
|
#include "WaveOut.h"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
WaveOutBuffer::WaveOutBuffer(WaveOut *waveOut, IBuffer *buffer, IPlayer *player)
|
WaveOutBuffer::WaveOutBuffer(WaveOut *waveOut, IBuffer *buffer, IPlayer *player)
|
||||||
: waveOut(waveOut)
|
: waveOut(waveOut)
|
||||||
, buffer(buffer)
|
, buffer(buffer)
|
||||||
@ -44,9 +46,9 @@ WaveOutBuffer::WaveOutBuffer(WaveOut *waveOut, IBuffer *buffer, IPlayer *player)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void WaveOutBuffer::Initialize() {
|
void WaveOutBuffer::Initialize() {
|
||||||
this->header.dwBufferLength = this->buffer->Samples() * this->buffer->Channels()*sizeof(float);
|
this->header.dwBufferLength = this->buffer->Samples() * this->buffer->Channels() * sizeof(float);
|
||||||
this->header.lpData = (LPSTR)this->buffer->BufferPointer();
|
this->header.lpData = (LPSTR) this->buffer->BufferPointer();
|
||||||
this->header.dwUser = (DWORD_PTR)this;
|
this->header.dwUser = (DWORD_PTR) this;
|
||||||
this->header.dwBytesRecorded = 0;
|
this->header.dwBytesRecorded = 0;
|
||||||
this->header.dwFlags = 0;
|
this->header.dwFlags = 0;
|
||||||
this->header.dwLoops = 0;
|
this->header.dwLoops = 0;
|
||||||
@ -57,14 +59,13 @@ void WaveOutBuffer::Initialize() {
|
|||||||
if (result != MMSYSERR_NOERROR) {
|
if (result != MMSYSERR_NOERROR) {
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
this->header.dwFlags |= WHDR_DONE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WaveOutBuffer::Destroy() {
|
void WaveOutBuffer::Destroy() {
|
||||||
if (!this->destroyed) {
|
if (!this->destroyed) {
|
||||||
if (this->waveOut->waveHandle && this->header.dwFlags & WHDR_PREPARED) {
|
if (this->waveOut->waveHandle && this->header.dwFlags & WHDR_PREPARED) {
|
||||||
waveOutUnprepareHeader(this->waveOut->waveHandle, &this->header, sizeof(WAVEHDR));
|
waveOutUnprepareHeader(this->waveOut->waveHandle, &this->header, sizeof(WAVEHDR));
|
||||||
|
this->header.dwFlags = WHDR_DONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
this->player->Notify();
|
this->player->Notify();
|
||||||
|
@ -186,7 +186,9 @@ void Player::ThreadLoop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TODO: why do we release buffers from the output here?? */
|
/* TODO: why do we release buffers from the output here?? this appears to be
|
||||||
|
an old hack to fix up some WaveOut issues where pending buffers need to be
|
||||||
|
released before the device can be closed. we can probably remove this now. */
|
||||||
this->output->ReleaseBuffers();
|
this->output->ReleaseBuffers();
|
||||||
|
|
||||||
BufferPtr buffer;
|
BufferPtr buffer;
|
||||||
@ -207,6 +209,8 @@ void Player::ThreadLoop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* TODO CLEAN UP AND DOCUMENT */
|
||||||
|
|
||||||
/* if we have a buffer available, let's try to send it to the output device */
|
/* if we have a buffer available, let's try to send it to the output device */
|
||||||
if (buffer) {
|
if (buffer) {
|
||||||
{
|
{
|
||||||
@ -232,7 +236,7 @@ void Player::ThreadLoop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else{
|
else {
|
||||||
// Buffer send to output
|
// Buffer send to output
|
||||||
boost::mutex::scoped_lock lock(this->mutex);
|
boost::mutex::scoped_lock lock(this->mutex);
|
||||||
if(!this->bufferQueue.empty()) {
|
if(!this->bufferQueue.empty()) {
|
||||||
@ -246,6 +250,8 @@ void Player::ThreadLoop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* END TODO CLEAN UP AND DOCUMENT */
|
||||||
|
|
||||||
/* if we're unable to obtain a buffer, it means we're out of data and the
|
/* if we're unable to obtain a buffer, it means we're out of data and the
|
||||||
player is finished. terminate the thread. */
|
player is finished. terminate the thread. */
|
||||||
else {
|
else {
|
||||||
|
@ -38,6 +38,7 @@
|
|||||||
#include <core/PluginFactory.h>
|
#include <core/PluginFactory.h>
|
||||||
|
|
||||||
using namespace musik::core::audio;
|
using namespace musik::core::audio;
|
||||||
|
using musik::core::PluginFactory;
|
||||||
|
|
||||||
Stream::Stream(unsigned int options)
|
Stream::Stream(unsigned int options)
|
||||||
:preferedBufferSampleSize(4096)
|
:preferedBufferSampleSize(4096)
|
||||||
@ -45,12 +46,9 @@ Stream::Stream(unsigned int options)
|
|||||||
,decoderSampleRate(0)
|
,decoderSampleRate(0)
|
||||||
,decoderSamplePosition(0)
|
,decoderSamplePosition(0)
|
||||||
{
|
{
|
||||||
// Get all DSPs
|
if((this->options&NoDSP) == 0){
|
||||||
// TODO: fixing PluginFactory
|
typedef PluginFactory::DestroyDeleter<IDSP> Deleter;
|
||||||
if( (this->options&NoDSP)==0){
|
this->dsps = PluginFactory::Instance().QueryInterface<IDSP, Deleter>("GetDSP");
|
||||||
this->dsps = musik::core::PluginFactory::Instance().QueryInterface<
|
|
||||||
IDSP,
|
|
||||||
musik::core::PluginFactory::DestroyDeleter<IDSP> >("GetDSP");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,9 +60,7 @@ StreamPtr Stream::Create(unsigned int options){
|
|||||||
return StreamPtr(new Stream(options));
|
return StreamPtr(new Stream(options));
|
||||||
}
|
}
|
||||||
|
|
||||||
BufferPtr Stream::NextBuffer(){
|
BufferPtr Stream::NextBuffer() {
|
||||||
|
|
||||||
// Decode a new buffer
|
|
||||||
return this->GetNextBuffer();
|
return this->GetNextBuffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,110 +78,104 @@ double Stream::SetPosition(double seconds){
|
|||||||
}
|
}
|
||||||
return newPosition;
|
return newPosition;
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
void Stream::SetMaxCacheLength(double seconds){
|
|
||||||
}
|
|
||||||
|
|
||||||
void Stream::SetPreferedBufferSampleSize(long samples){
|
bool Stream::OpenStream(utfstring uri) {
|
||||||
}
|
/* use our file stream abstraction to open the data at the
|
||||||
*/
|
specified URI */
|
||||||
bool Stream::OpenStream(utfstring uri){
|
this->fileStream = musik::core::filestreams::Factory::OpenFile(uri.c_str());
|
||||||
|
if (!this->fileStream) {
|
||||||
// Open the filestream
|
|
||||||
this->fileStream = musik::core::filestreams::Factory::OpenFile(uri.c_str());
|
|
||||||
if(!this->fileStream){
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look up what DecoderFactory to use
|
/* find a DecoderFactory we can use for this type of data*/
|
||||||
StreamHelper::DecoderFactoryPtr decoderFactory;
|
StreamHelper::DecoderFactoryPtr decoderFactory;
|
||||||
for(StreamHelper::DecoderFactories::iterator decoderFactoryIt=Stream::Helper()->decoderFactories.begin();decoderFactoryIt!=Stream::Helper()->decoderFactories.end() && !decoderFactory;++decoderFactoryIt){
|
StreamHelper::DecoderFactories::iterator factories = Stream::Helper()->decoderFactories.begin();
|
||||||
if( (*decoderFactoryIt)->CanHandle(this->fileStream->Type())){
|
StreamHelper::DecoderFactories::iterator end = Stream::Helper()->decoderFactories.end();
|
||||||
decoderFactory = (*decoderFactoryIt);
|
|
||||||
|
for( ; factories != end && !decoderFactory; ++factories) {
|
||||||
|
if((*factories)->CanHandle(this->fileStream->Type())){
|
||||||
|
decoderFactory = (*factories);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!decoderFactory){
|
if (!decoderFactory) {
|
||||||
// We have failed to get a working decoderFactory
|
/* nothing can decode this type of file */
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IDecoder *decoder = decoderFactory->CreateDecoder();
|
||||||
// Create the decoder
|
if (!decoder) {
|
||||||
IDecoder *decoderPtr = decoderFactory->CreateDecoder();
|
/* shouldn't ever happen, the factory said it can handle this file */
|
||||||
if(!decoderPtr){
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
typedef PluginFactory::DestroyDeleter<IDecoder> Deleter;
|
||||||
|
|
||||||
// Open the decoder
|
/* ask the decoder to open the data stream. if it returns true we're
|
||||||
typedef musik::core::PluginFactory::DestroyDeleter<IDecoder> IDecoderDeleter;
|
good to start pulling data out of it! */
|
||||||
this->decoder.reset(decoderPtr,IDecoderDeleter());
|
this->decoder.reset(decoder, Deleter());
|
||||||
if( !this->decoder->Open(this->fileStream.get()) ){
|
if (!this->decoder->Open(this->fileStream.get())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BufferPtr Stream::GetNextDecoderBuffer(){
|
BufferPtr Stream::GetNextDecoderBuffer() {
|
||||||
// First get a buffer
|
/* get a spare buffer, then ask the decoder for some data */
|
||||||
BufferPtr buffer = this->NewBuffer();
|
BufferPtr buffer = this->NewBuffer();
|
||||||
|
if(!this->decoder->GetBuffer(buffer.get())) {
|
||||||
// Get the buffer from the decoder
|
|
||||||
if(!this->decoder->GetBuffer(buffer.get())){
|
|
||||||
// Nothing to decode left, return a empty buffer
|
|
||||||
return BufferPtr();
|
return BufferPtr();
|
||||||
}
|
}
|
||||||
|
|
||||||
// We need to save the decoders samplerate to be able to calculate the current time-position
|
/* remember the samplerate so we can calculate the current time-position */
|
||||||
if(!this->decoderSampleRate){
|
if(!this->decoderSampleRate){
|
||||||
this->decoderSampleRate = buffer->SampleRate();
|
this->decoderSampleRate = buffer->SampleRate();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate the current sample position
|
/* calculate the current offset, in samples */
|
||||||
this->decoderSamplePosition += buffer->Samples();
|
this->decoderSamplePosition += buffer->Samples();
|
||||||
|
|
||||||
// Save the position (seconds) in the buffer
|
/* calculate the position (seconds) in the buffer */
|
||||||
buffer->position = ((double)this->decoderSamplePosition)/((double)this->decoderSampleRate);
|
buffer->position = ((double)this->decoderSamplePosition) / ((double)this->decoderSampleRate);
|
||||||
|
|
||||||
return buffer;
|
return buffer;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BufferPtr Stream::GetNextBuffer(){
|
BufferPtr Stream::GetNextBuffer() {
|
||||||
// First get the next decoded buffer
|
/* ask the decoder for the next buffer */
|
||||||
BufferPtr currentBuffer = this->GetNextDecoderBuffer();
|
BufferPtr currentBuffer = this->GetNextDecoderBuffer();
|
||||||
|
|
||||||
if(currentBuffer){
|
if(currentBuffer) {
|
||||||
/////////////////////////////////////////////
|
/* try to fill the buffer to its optimal size; if the decoder didn't return
|
||||||
// Lets check if the buffer is too small
|
a full buffer, ask it for some more data. */
|
||||||
bool moreBuffers(true);
|
bool moreBuffers = true;
|
||||||
while(currentBuffer->Samples()<this->preferedBufferSampleSize && moreBuffers){
|
while (currentBuffer->Samples() < this->preferedBufferSampleSize && moreBuffers) {
|
||||||
BufferPtr appendBuffer = this->GetNextDecoderBuffer();
|
BufferPtr bufferToAppend = this->GetNextDecoderBuffer();
|
||||||
if(appendBuffer){
|
if (bufferToAppend) {
|
||||||
currentBuffer->Append(appendBuffer);
|
currentBuffer->Append(bufferToAppend);
|
||||||
this->DeleteBuffer(appendBuffer);
|
this->DeleteBuffer(bufferToAppend);
|
||||||
}else{
|
}
|
||||||
|
else {
|
||||||
moreBuffers = false;
|
moreBuffers = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/////////////////////////////////////////////
|
|
||||||
|
|
||||||
BufferPtr oldBuffer = this->NewBuffer();
|
/* let DSP plugins process the buffer */
|
||||||
|
if (this->dsps.size() > 0) {
|
||||||
|
BufferPtr oldBuffer = this->NewBuffer();
|
||||||
|
|
||||||
// Now lets loop through all DSP plugins
|
for (Dsps::iterator dsp = this->dsps.begin(); dsp != this->dsps.end(); ++dsp) {
|
||||||
for(Dsps::iterator dsp=this->dsps.begin();dsp!=this->dsps.end();++dsp){
|
oldBuffer->CopyFormat(currentBuffer);
|
||||||
oldBuffer->CopyFormat(currentBuffer);
|
oldBuffer->position = currentBuffer->position;
|
||||||
oldBuffer->position = currentBuffer->position;
|
|
||||||
|
|
||||||
if( (*dsp)->ProcessBuffers(currentBuffer.get(),oldBuffer.get()) ){
|
if ((*dsp)->ProcessBuffers(currentBuffer.get(), oldBuffer.get())) {
|
||||||
// Success in processing DSP, swap the buffers
|
currentBuffer.swap(oldBuffer);
|
||||||
currentBuffer.swap(oldBuffer);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
this->DeleteBuffer(oldBuffer);
|
this->DeleteBuffer(oldBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -203,7 +193,7 @@ BufferPtr Stream::NewBuffer(){
|
|||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Stream::DeleteBuffer(BufferPtr oldBuffer){
|
void Stream::DeleteBuffer(BufferPtr oldBuffer) {
|
||||||
this->availableBuffers.push_back(oldBuffer);
|
this->availableBuffers.push_back(oldBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -215,20 +205,22 @@ Stream::StreamHelperPtr Stream::Helper(){
|
|||||||
return helper;
|
return helper;
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream::StreamHelper::StreamHelper(){
|
Stream::StreamHelper::StreamHelper() {
|
||||||
// Look up all DecoderFactories
|
PluginFactory::DestroyDeleter<IDecoderFactory> typedef Deleter;
|
||||||
this->decoderFactories = musik::core::PluginFactory::Instance().QueryInterface<
|
|
||||||
IDecoderFactory,
|
this->decoderFactories = PluginFactory::Instance()
|
||||||
musik::core::PluginFactory::DestroyDeleter<IDecoderFactory> >("GetDecoderFactory");
|
.QueryInterface<IDecoderFactory, Deleter>("GetDecoderFactory");
|
||||||
}
|
}
|
||||||
|
|
||||||
double Stream::DecoderProgress(){
|
double Stream::DecoderProgress() {
|
||||||
if(this->fileStream){
|
if (this->fileStream) {
|
||||||
long fileSize = this->fileStream->Filesize();
|
long fileSize = this->fileStream->Filesize();
|
||||||
long filePosition = this->fileStream->Position();
|
long filePosition = this->fileStream->Position();
|
||||||
if(fileSize && filePosition){
|
|
||||||
return ((double)filePosition)/((double)fileSize);
|
if (fileSize && filePosition) {
|
||||||
|
return ((double) filePosition) / ((double) fileSize);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -41,8 +41,6 @@
|
|||||||
#include <core/Common.h>
|
#include <core/Common.h>
|
||||||
#include <core/config_filesystem.h>
|
#include <core/config_filesystem.h>
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
#ifdef UTF_WIDECHAR
|
#ifdef UTF_WIDECHAR
|
||||||
#define UTFFopen _wfopen
|
#define UTFFopen _wfopen
|
||||||
typedef fpos_t stdioPositionType;
|
typedef fpos_t stdioPositionType;
|
||||||
@ -51,11 +49,8 @@ typedef fpos_t stdioPositionType;
|
|||||||
typedef fpos_t stdioPositionType;
|
typedef fpos_t stdioPositionType;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
using namespace musik::core::filestreams;
|
using namespace musik::core::filestreams;
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////
|
|
||||||
LocalFileStream::LocalFileStream()
|
LocalFileStream::LocalFileStream()
|
||||||
: file(NULL)
|
: file(NULL)
|
||||||
, filesize(-1)
|
, filesize(-1)
|
||||||
@ -82,10 +77,10 @@ bool LocalFileStream::Open(const utfchar *filename,unsigned int options){
|
|||||||
std::cerr << "File not a regular file" << std::endl;
|
std::cerr << "File not a regular file" << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
this->filesize = (long)boost::filesystem::file_size(file);
|
this->filesize = (long)boost::filesystem::file_size(file);
|
||||||
this->extension = file.extension().wstring();
|
this->extension = file.extension().wstring();
|
||||||
this->file = UTFFopen(filename,UTF("rb"));
|
this->file = UTFFopen(filename,UTF("rb"));
|
||||||
this->fd = new boost::iostreams::file_descriptor(file);
|
this->fd = new boost::iostreams::file_descriptor(file);
|
||||||
this->fileStream = new boost::iostreams::stream<boost::iostreams::file_descriptor>(*this->fd);
|
this->fileStream = new boost::iostreams::stream<boost::iostreams::file_descriptor>(*this->fd);
|
||||||
this->fileStream->exceptions(std::ios_base::eofbit | std::ios_base::failbit | std::ios_base::badbit);
|
this->fileStream->exceptions(std::ios_base::eofbit | std::ios_base::failbit | std::ios_base::badbit);
|
||||||
|
|
||||||
@ -128,8 +123,6 @@ PositionType LocalFileStream::Read(void* buffer,PositionType readBytes) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool LocalFileStream::SetPosition(PositionType position) {
|
bool LocalFileStream::SetPosition(PositionType position) {
|
||||||
/*stdioPositionType newPosition = (stdioPositionType)position;
|
|
||||||
return fsetpos(this->file,&newPosition)==0;*/
|
|
||||||
try {
|
try {
|
||||||
this->fileStream->clear();
|
this->fileStream->clear();
|
||||||
this->fileStream->seekg(position);
|
this->fileStream->seekg(position);
|
||||||
@ -142,16 +135,10 @@ bool LocalFileStream::SetPosition(PositionType position) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
PositionType LocalFileStream::Position() {
|
PositionType LocalFileStream::Position() {
|
||||||
/*stdioPositionType currentPosition(0);
|
|
||||||
if(fgetpos(this->file,¤tPosition)==0){
|
|
||||||
return (PositionType)currentPosition;
|
|
||||||
}
|
|
||||||
return -1;*/
|
|
||||||
return this->fileStream->tellg();
|
return this->fileStream->tellg();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool LocalFileStream::Eof() {
|
bool LocalFileStream::Eof() {
|
||||||
//return feof(this->file)!=0;
|
|
||||||
return this->fileStream->eof();
|
return this->fileStream->eof();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,17 +37,12 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
namespace musik { namespace core {
|
namespace musik { namespace core {
|
||||||
|
class IPlugin{
|
||||||
class IPlugin{
|
|
||||||
protected:
|
|
||||||
virtual ~IPlugin() {};
|
|
||||||
public:
|
public:
|
||||||
virtual void Destroy() =0;
|
virtual void Destroy() = 0;
|
||||||
|
virtual const utfchar* Name() = 0;
|
||||||
virtual const utfchar* Name()=0;
|
virtual const utfchar* Version() = 0;
|
||||||
virtual const utfchar* Version()=0;
|
virtual const utfchar* Author() = 0;
|
||||||
virtual const utfchar* Author()=0;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} }
|
} }
|
||||||
|
|
||||||
|
58
src/core/sdk/IRequestParser.h
Normal file
58
src/core/sdk/IRequestParser.h
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// License Agreement:
|
||||||
|
//
|
||||||
|
// The following are Copyright © 2008, Daniel Önnerby
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer.
|
||||||
|
//
|
||||||
|
// * Redistributions in binary form must reproduce the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer in the
|
||||||
|
// documentation and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// * Neither the name of the author nor the names of other contributors may
|
||||||
|
// be used to endorse or promote products derived from this software
|
||||||
|
// without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
// POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
//
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <core/config.h>
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
namespace musik{ namespace core{ namespace http{
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
class IRequestParser{
|
||||||
|
public:
|
||||||
|
virtual const char* Attribute(const char* key)=0;
|
||||||
|
virtual const char* Path()=0;
|
||||||
|
virtual const char* SubPath(int position)=0;
|
||||||
|
};
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
} } } // musik::core:http
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
61
src/core/sdk/IRequestPlugin.h
Normal file
61
src/core/sdk/IRequestPlugin.h
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// License Agreement:
|
||||||
|
//
|
||||||
|
// The following are Copyright © 2008, Daniel Önnerby
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer.
|
||||||
|
//
|
||||||
|
// * Redistributions in binary form must reproduce the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer in the
|
||||||
|
// documentation and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// * Neither the name of the author nor the names of other contributors may
|
||||||
|
// be used to endorse or promote products derived from this software
|
||||||
|
// without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
// POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
//
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <core/config.h>
|
||||||
|
#include "IResponder.h"
|
||||||
|
#include "IRequestParser.h"
|
||||||
|
#include "ITrack.h"
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
namespace musik{ namespace core{ namespace http{
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
class IRequestPlugin{
|
||||||
|
public:
|
||||||
|
virtual void Destroy()=0;
|
||||||
|
virtual const char* WatchPath()=0;
|
||||||
|
virtual void Execute(musik::core::http::IResponder* responder,musik::core::http::IRequestParser* request,musik::core::ITrack* track)=0;
|
||||||
|
};
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
} } }
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
58
src/core/sdk/IResponder.h
Normal file
58
src/core/sdk/IResponder.h
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// License Agreement:
|
||||||
|
//
|
||||||
|
// The following are Copyright © 2008, Daniel Önnerby
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer.
|
||||||
|
//
|
||||||
|
// * Redistributions in binary form must reproduce the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer in the
|
||||||
|
// documentation and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// * Neither the name of the author nor the names of other contributors may
|
||||||
|
// be used to endorse or promote products derived from this software
|
||||||
|
// without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
// POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
//
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <core/config.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
namespace musik{ namespace core{ namespace http{
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
class IResponder{
|
||||||
|
public:
|
||||||
|
virtual void SendContent(const char* buffer,const std::size_t bufferSize)=0;
|
||||||
|
virtual bool Exited()=0;
|
||||||
|
};
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
} } }
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user