Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
M
miniaudio
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Locked Files
Issues
0
Issues
0
List
Boards
Labels
Service Desk
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Security & Compliance
Security & Compliance
Dependency List
License Compliance
Packages
Packages
List
Container Registry
Analytics
Analytics
CI / CD
Code Review
Insights
Issues
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
MyCard
miniaudio
Commits
37491c1f
Commit
37491c1f
authored
Nov 16, 2017
by
r-lyeh
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
extend with PD MOD/XM player
parent
975b8ee3
Changes
3
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
4375 additions
and
0 deletions
+4375
-0
examples/multi_playback.c
examples/multi_playback.c
+111
-0
extras/jar_mod.h
extras/jar_mod.h
+1594
-0
extras/jar_xm.h
extras/jar_xm.h
+2670
-0
No files found.
examples/multi_playback.c
0 → 100644
View file @
37491c1f
#define MAL_IMPLEMENTATION
#include "../mini_al.h"
#define DR_WAV_IMPLEMENTATION
#include "../extras/dr_wav.h"
#define JAR_MOD_IMPLEMENTATION
#include "../extras/jar_mod.h"
#undef DEBUG
#define JAR_XM_IMPLEMENTATION
#include "../extras/jar_xm.h"
#include <stdio.h>
// This is the function that's used for sending more data to the device for playback.
mal_uint32
on_send_wav_frames_to_device
(
mal_device
*
pDevice
,
mal_uint32
frameCount
,
void
*
pSamples
)
{
drwav
*
pWav
=
(
drwav
*
)
pDevice
->
pUserData
;
if
(
pWav
==
NULL
)
{
return
0
;
}
return
(
mal_uint32
)
drwav_read_s16
(
pWav
,
frameCount
*
pDevice
->
channels
,
(
mal_int16
*
)
pSamples
)
/
pDevice
->
channels
;
}
mal_uint32
on_send_mod_frames_to_device
(
mal_device
*
pDevice
,
mal_uint32
frameCount
,
void
*
pSamples
)
{
jar_mod_context_t
*
pMod
=
(
jar_mod_context_t
*
)
pDevice
->
pUserData
;
if
(
pMod
==
NULL
)
{
return
0
;
}
jar_mod_fillbuffer
(
pMod
,
(
mal_int16
*
)
pSamples
,
frameCount
*
pDevice
->
channels
,
0
);
return
(
mal_uint32
)(
frameCount
*
pDevice
->
channels
);
}
mal_uint32
on_send_xm_frames_to_device
(
mal_device
*
pDevice
,
mal_uint32
frameCount
,
void
*
pSamples
)
{
jar_xm_context_t
*
pXM
=
(
jar_xm_context_t
*
)
pDevice
->
pUserData
;
if
(
pXM
==
NULL
)
{
return
0
;
}
jar_xm_generate_samples_16bit
(
pXM
,
(
short
*
)
pSamples
,
frameCount
*
pDevice
->
channels
);
// (48000 / 60) / 2
return
(
mal_uint32
)(
frameCount
*
pDevice
->
channels
);
}
int
main
(
int
argc
,
char
**
argv
)
{
system
(
"pause"
);
int
exitcode
=
0
;
if
(
argc
<
2
)
{
printf
(
"No input file."
);
return
-
1
;
}
enum
{
UNK
,
WAV
,
MOD
,
XM
}
type
=
UNK
;
jar_mod_context_t
mod
;
jar_mod_init
(
&
mod
);
jar_xm_context_t
*
xm
=
0
;
drwav
wav
;
if
(
type
==
UNK
&&
drwav_init_file
(
&
wav
,
argv
[
1
])
)
type
=
WAV
;
if
(
type
==
UNK
&&
(
jar_xm_create_context_from_file
(
&
xm
,
48000
,
argv
[
1
])
==
0
))
type
=
XM
;
if
(
type
==
UNK
&&
(
jar_mod_load_file
(
&
mod
,
argv
[
1
])
!=
0
)
)
type
=
MOD
;
if
(
type
==
UNK
)
{
printf
(
"Not a valid input file."
);
exitcode
=
-
2
;
goto
end
;
}
mal_context
context
;
if
(
mal_context_init
(
NULL
,
0
,
NULL
,
&
context
)
!=
MAL_SUCCESS
)
{
printf
(
"Failed to initialize context."
);
exitcode
=
-
3
;
goto
end
;
}
mal_device_config
config
;
if
(
type
==
WAV
)
config
=
mal_device_config_init_playback
(
mal_format_s16
,
wav
.
channels
,
wav
.
sampleRate
,
on_send_wav_frames_to_device
);
if
(
type
==
MOD
)
config
=
mal_device_config_init_playback
(
mal_format_s16
,
2
,
mod
.
playrate
,
on_send_mod_frames_to_device
);
if
(
type
==
XM
)
config
=
mal_device_config_init_playback
(
mal_format_s16
,
2
,
48000
,
on_send_xm_frames_to_device
);
mal_device
device
;
if
(
mal_device_init
(
&
context
,
mal_device_type_playback
,
NULL
,
&
config
,
type
==
WAV
?
(
void
*
)
&
wav
:
type
==
MOD
?
(
void
*
)
&
mod
:
(
void
*
)
xm
,
&
device
)
!=
MAL_SUCCESS
)
{
printf
(
"Failed to open playback device."
);
mal_context_uninit
(
&
context
);
exitcode
=
-
4
;
goto
end
;
}
mal_device_start
(
&
device
);
printf
(
"Press Enter to quit..."
);
getchar
();
mal_device_uninit
(
&
device
);
mal_context_uninit
(
&
context
);
end:
;
drwav_uninit
(
&
wav
);
jar_mod_unload
(
&
mod
);
if
(
xm
)
jar_xm_free_context
(
xm
);
return
exitcode
;
}
extras/jar_mod.h
0 → 100644
View file @
37491c1f
// jar_mod.h - v0.01 - public domain C0 - Joshua Reisenauer
//
// HISTORY:
//
// v0.01 2016-03-12 Setup
//
//
// USAGE:
//
// In ONE source file, put:
//
// #define JAR_MOD_IMPLEMENTATION
// #include "jar_mod.h"
//
// Other source files should just include jar_mod.h
//
// SAMPLE CODE:
// jar_mod_context_t modctx;
// short samplebuff[4096];
// bool bufferFull = false;
// int intro_load(void)
// {
// jar_mod_init(&modctx);
// jar_mod_load_file(&modctx, "file.mod");
// return 1;
// }
// int intro_unload(void)
// {
// jar_mod_unload(&modctx);
// return 1;
// }
// int intro_tick(long counter)
// {
// if(!bufferFull)
// {
// jar_mod_fillbuffer(&modctx, samplebuff, 4096, 0);
// bufferFull=true;
// }
// if(IsKeyDown(KEY_ENTER))
// return 1;
// return 0;
// }
//
//
// LISCENSE:
//
// Written by: Jean-Franois DEL NERO (http://hxc2001.com/) <Email : jeanfrancoisdelnero <> free.fr>
// Adapted to jar_mod by: Joshua Adam Reisenauer <kd7tck@gmail.com>
// This program is free software. It comes without any warranty, to the
// extent permitted by applicable law. You can redistribute it and/or
// modify it under the terms of the Do What The Fuck You Want To Public
// License, Version 2, as published by Sam Hocevar. See
// http://sam.zoy.org/wtfpl/COPYING for more details.
///////////////////////////////////////////////////////////////////////////////////
// HxCMOD Core API:
// -------------------------------------------
// int jar_mod_init(jar_mod_context_t * modctx)
//
// - Initialize the jar_mod_context_t buffer. Must be called before doing anything else.
// Return 1 if success. 0 in case of error.
// -------------------------------------------
// mulong jar_mod_load_file(jar_mod_context_t * modctx, const char* filename)
//
// - "Load" a MOD from file, context must already be initialized.
// Return size of file in bytes.
// -------------------------------------------
// void jar_mod_fillbuffer( jar_mod_context_t * modctx, short * outbuffer, unsigned long nbsample, jar_mod_tracker_buffer_state * trkbuf )
//
// - Generate and return the next samples chunk to outbuffer.
// nbsample specify the number of stereo 16bits samples you want.
// The output format is by default signed 48000Hz 16-bit Stereo PCM samples, otherwise it is changed with jar_mod_setcfg().
// The output buffer size in bytes must be equal to ( nbsample * 2 * channels ).
// The optional trkbuf parameter can be used to get detailed status of the player. Put NULL/0 is unused.
// -------------------------------------------
// void jar_mod_unload( jar_mod_context_t * modctx )
// - "Unload" / clear the player status.
// -------------------------------------------
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_JAR_MOD_H
#define INCLUDE_JAR_MOD_H
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h> // comment this line out if you have bool defined somewhere else
#ifdef __cplusplus
extern
"C"
{
#endif
// Basic type
typedef
unsigned
char
muchar
;
typedef
unsigned
short
muint
;
typedef
short
mint
;
typedef
unsigned
long
mulong
;
#define NUMMAXCHANNELS 32
#define MAXNOTES 12*12
#define DEFAULT_SAMPLE_RATE 48000
//
// MOD file structures
//
#pragma pack(1)
typedef
struct
{
muchar
name
[
22
];
muint
length
;
muchar
finetune
;
muchar
volume
;
muint
reppnt
;
muint
replen
;
}
sample
;
typedef
struct
{
muchar
sampperiod
;
muchar
period
;
muchar
sampeffect
;
muchar
effect
;
}
note
;
typedef
struct
{
muchar
title
[
20
];
sample
samples
[
31
];
muchar
length
;
// length of tablepos
muchar
protracker
;
muchar
patterntable
[
128
];
muchar
signature
[
4
];
muchar
speed
;
}
module
;
#pragma pack()
//
// HxCMod Internal structures
//
typedef
struct
{
char
*
sampdata
;
muint
sampnum
;
muint
length
;
muint
reppnt
;
muint
replen
;
mulong
samppos
;
muint
period
;
muchar
volume
;
mulong
ticks
;
muchar
effect
;
muchar
parameffect
;
muint
effect_code
;
mint
decalperiod
;
mint
portaspeed
;
mint
portaperiod
;
mint
vibraperiod
;
mint
Arpperiods
[
3
];
muchar
ArpIndex
;
mint
oldk
;
muchar
volumeslide
;
muchar
vibraparam
;
muchar
vibrapointeur
;
muchar
finetune
;
muchar
cut_param
;
muint
patternloopcnt
;
muint
patternloopstartpoint
;
}
channel
;
typedef
struct
{
module
song
;
char
*
sampledata
[
31
];
note
*
patterndata
[
128
];
mulong
playrate
;
muint
tablepos
;
muint
patternpos
;
muint
patterndelay
;
muint
jump_loop_effect
;
muchar
bpm
;
mulong
patternticks
;
mulong
patterntickse
;
mulong
patternticksaim
;
mulong
sampleticksconst
;
mulong
samplenb
;
channel
channels
[
NUMMAXCHANNELS
];
muint
number_of_channels
;
muint
fullperiod
[
MAXNOTES
*
8
];
muint
mod_loaded
;
mint
last_r_sample
;
mint
last_l_sample
;
mint
stereo
;
mint
stereo_separation
;
mint
bits
;
mint
filter
;
muchar
*
modfile
;
// the raw mod file
mulong
modfilesize
;
muint
loopcount
;
}
jar_mod_context_t
;
//
// Player states structures
//
typedef
struct
track_state_
{
unsigned
char
instrument_number
;
unsigned
short
cur_period
;
unsigned
char
cur_volume
;
unsigned
short
cur_effect
;
unsigned
short
cur_parameffect
;
}
track_state
;
typedef
struct
tracker_state_
{
int
number_of_tracks
;
int
bpm
;
int
speed
;
int
cur_pattern
;
int
cur_pattern_pos
;
int
cur_pattern_table_pos
;
unsigned
int
buf_index
;
track_state
tracks
[
32
];
}
tracker_state
;
typedef
struct
tracker_state_instrument_
{
char
name
[
22
];
int
active
;
}
tracker_state_instrument
;
typedef
struct
jar_mod_tracker_buffer_state_
{
int
nb_max_of_state
;
int
nb_of_state
;
int
cur_rd_index
;
int
sample_step
;
char
name
[
64
];
tracker_state_instrument
instruments
[
31
];
tracker_state
*
track_state_buf
;
}
jar_mod_tracker_buffer_state
;
bool
jar_mod_init
(
jar_mod_context_t
*
modctx
);
bool
jar_mod_setcfg
(
jar_mod_context_t
*
modctx
,
int
samplerate
,
int
bits
,
int
stereo
,
int
stereo_separation
,
int
filter
);
void
jar_mod_fillbuffer
(
jar_mod_context_t
*
modctx
,
short
*
outbuffer
,
unsigned
long
nbsample
,
jar_mod_tracker_buffer_state
*
trkbuf
);
void
jar_mod_unload
(
jar_mod_context_t
*
modctx
);
mulong
jar_mod_load_file
(
jar_mod_context_t
*
modctx
,
const
char
*
filename
);
mulong
jar_mod_current_samples
(
jar_mod_context_t
*
modctx
);
mulong
jar_mod_max_samples
(
jar_mod_context_t
*
modctx
);
void
jar_mod_seek_start
(
jar_mod_context_t
*
ctx
);
#ifdef __cplusplus
}
#endif
//--------------------------------------------------------------------
//-------------------------------------------------------------------------------
#ifdef JAR_MOD_IMPLEMENTATION
// Effects list
#define EFFECT_ARPEGGIO 0x0 // Supported
#define EFFECT_PORTAMENTO_UP 0x1 // Supported
#define EFFECT_PORTAMENTO_DOWN 0x2 // Supported
#define EFFECT_TONE_PORTAMENTO 0x3 // Supported
#define EFFECT_VIBRATO 0x4 // Supported
#define EFFECT_VOLSLIDE_TONEPORTA 0x5 // Supported
#define EFFECT_VOLSLIDE_VIBRATO 0x6 // Supported
#define EFFECT_VOLSLIDE_TREMOLO 0x7 // - TO BE DONE -
#define EFFECT_SET_PANNING 0x8 // - TO BE DONE -
#define EFFECT_SET_OFFSET 0x9 // Supported
#define EFFECT_VOLUME_SLIDE 0xA // Supported
#define EFFECT_JUMP_POSITION 0xB // Supported
#define EFFECT_SET_VOLUME 0xC // Supported
#define EFFECT_PATTERN_BREAK 0xD // Supported
#define EFFECT_EXTENDED 0xE
#define EFFECT_E_FINE_PORTA_UP 0x1 // Supported
#define EFFECT_E_FINE_PORTA_DOWN 0x2 // Supported
#define EFFECT_E_GLISSANDO_CTRL 0x3 // - TO BE DONE -
#define EFFECT_E_VIBRATO_WAVEFORM 0x4 // - TO BE DONE -
#define EFFECT_E_SET_FINETUNE 0x5 // - TO BE DONE -
#define EFFECT_E_PATTERN_LOOP 0x6 // Supported
#define EFFECT_E_TREMOLO_WAVEFORM 0x7 // - TO BE DONE -
#define EFFECT_E_SET_PANNING_2 0x8 // - TO BE DONE -
#define EFFECT_E_RETRIGGER_NOTE 0x9 // - TO BE DONE -
#define EFFECT_E_FINE_VOLSLIDE_UP 0xA // Supported
#define EFFECT_E_FINE_VOLSLIDE_DOWN 0xB // Supported
#define EFFECT_E_NOTE_CUT 0xC // Supported
#define EFFECT_E_NOTE_DELAY 0xD // - TO BE DONE -
#define EFFECT_E_PATTERN_DELAY 0xE // Supported
#define EFFECT_E_INVERT_LOOP 0xF // - TO BE DONE -
#define EFFECT_SET_SPEED 0xF0 // Supported
#define EFFECT_SET_TEMPO 0xF2 // Supported
#define PERIOD_TABLE_LENGTH MAXNOTES
#define FULL_PERIOD_TABLE_LENGTH ( PERIOD_TABLE_LENGTH * 8 )
static
const
short
periodtable
[]
=
{
27392
,
25856
,
24384
,
23040
,
21696
,
20480
,
19328
,
18240
,
17216
,
16256
,
15360
,
14496
,
13696
,
12928
,
12192
,
11520
,
10848
,
10240
,
9664
,
9120
,
8606
,
8128
,
7680
,
7248
,
6848
,
6464
,
6096
,
5760
,
5424
,
5120
,
4832
,
4560
,
4304
,
4064
,
3840
,
3624
,
3424
,
3232
,
3048
,
2880
,
2712
,
2560
,
2416
,
2280
,
2152
,
2032
,
1920
,
1812
,
1712
,
1616
,
1524
,
1440
,
1356
,
1280
,
1208
,
1140
,
1076
,
1016
,
960
,
906
,
856
,
808
,
762
,
720
,
678
,
640
,
604
,
570
,
538
,
508
,
480
,
453
,
428
,
404
,
381
,
360
,
339
,
320
,
302
,
285
,
269
,
254
,
240
,
226
,
214
,
202
,
190
,
180
,
170
,
160
,
151
,
143
,
135
,
127
,
120
,
113
,
107
,
101
,
95
,
90
,
85
,
80
,
75
,
71
,
67
,
63
,
60
,
56
,
53
,
50
,
47
,
45
,
42
,
40
,
37
,
35
,
33
,
31
,
30
,
28
,
27
,
25
,
24
,
22
,
21
,
20
,
19
,
18
,
17
,
16
,
15
,
14
,
13
,
13
,
12
,
11
,
11
,
10
,
9
,
9
,
8
,
8
,
7
,
7
};
static
const
short
sintable
[]
=
{
0
,
24
,
49
,
74
,
97
,
120
,
141
,
161
,
180
,
197
,
212
,
224
,
235
,
244
,
250
,
253
,
255
,
253
,
250
,
244
,
235
,
224
,
212
,
197
,
180
,
161
,
141
,
120
,
97
,
74
,
49
,
24
};
typedef
struct
modtype_
{
unsigned
char
signature
[
5
];
int
numberofchannels
;
}
modtype
;
modtype
modlist
[]
=
{
{
"M!K!"
,
4
},
{
"M.K."
,
4
},
{
"FLT4"
,
4
},
{
"FLT8"
,
8
},
{
"4CHN"
,
4
},
{
"6CHN"
,
6
},
{
"8CHN"
,
8
},
{
"10CH"
,
10
},
{
"12CH"
,
12
},
{
"14CH"
,
14
},
{
"16CH"
,
16
},
{
"18CH"
,
18
},
{
"20CH"
,
20
},
{
"22CH"
,
22
},
{
"24CH"
,
24
},
{
"26CH"
,
26
},
{
"28CH"
,
28
},
{
"30CH"
,
30
},
{
"32CH"
,
32
},
{
""
,
0
}
};
///////////////////////////////////////////////////////////////////////////////////
static
void
memcopy
(
void
*
dest
,
void
*
source
,
unsigned
long
size
)
{
unsigned
long
i
;
unsigned
char
*
d
,
*
s
;
d
=
(
unsigned
char
*
)
dest
;
s
=
(
unsigned
char
*
)
source
;
for
(
i
=
0
;
i
<
size
;
i
++
)
{
d
[
i
]
=
s
[
i
];
}
}
static
void
memclear
(
void
*
dest
,
unsigned
char
value
,
unsigned
long
size
)
{
unsigned
long
i
;
unsigned
char
*
d
;
d
=
(
unsigned
char
*
)
dest
;
for
(
i
=
0
;
i
<
size
;
i
++
)
{
d
[
i
]
=
value
;
}
}
static
int
memcompare
(
unsigned
char
*
buf1
,
unsigned
char
*
buf2
,
unsigned
int
size
)
{
unsigned
int
i
;
i
=
0
;
while
(
i
<
size
)
{
if
(
buf1
[
i
]
!=
buf2
[
i
])
{
return
0
;
}
i
++
;
}
return
1
;
}
static
int
getnote
(
jar_mod_context_t
*
mod
,
unsigned
short
period
,
int
finetune
)
{
int
i
;
for
(
i
=
0
;
i
<
FULL_PERIOD_TABLE_LENGTH
;
i
++
)
{
if
(
period
>=
mod
->
fullperiod
[
i
])
{
return
i
;
}
}
return
MAXNOTES
;
}
static
void
worknote
(
note
*
nptr
,
channel
*
cptr
,
char
t
,
jar_mod_context_t
*
mod
)
{
muint
sample
,
period
,
effect
,
operiod
;
muint
curnote
,
arpnote
;
sample
=
(
nptr
->
sampperiod
&
0xF0
)
|
(
nptr
->
sampeffect
>>
4
);
period
=
((
nptr
->
sampperiod
&
0xF
)
<<
8
)
|
nptr
->
period
;
effect
=
((
nptr
->
sampeffect
&
0xF
)
<<
8
)
|
nptr
->
effect
;
operiod
=
cptr
->
period
;
if
(
period
||
sample
)
{
if
(
sample
&&
sample
<
32
)
{
cptr
->
sampnum
=
sample
-
1
;
}
if
(
period
||
sample
)
{
cptr
->
sampdata
=
(
char
*
)
mod
->
sampledata
[
cptr
->
sampnum
];
cptr
->
length
=
mod
->
song
.
samples
[
cptr
->
sampnum
].
length
;
cptr
->
reppnt
=
mod
->
song
.
samples
[
cptr
->
sampnum
].
reppnt
;
cptr
->
replen
=
mod
->
song
.
samples
[
cptr
->
sampnum
].
replen
;
cptr
->
finetune
=
(
mod
->
song
.
samples
[
cptr
->
sampnum
].
finetune
)
&
0xF
;
if
(
effect
>>
8
!=
4
&&
effect
>>
8
!=
6
)
{
cptr
->
vibraperiod
=
0
;
cptr
->
vibrapointeur
=
0
;
}
}
if
(
(
sample
!=
0
)
&&
(
(
effect
>>
8
)
!=
EFFECT_VOLSLIDE_TONEPORTA
)
)
{
cptr
->
volume
=
mod
->
song
.
samples
[
cptr
->
sampnum
].
volume
;
cptr
->
volumeslide
=
0
;
}
if
(
(
(
effect
>>
8
)
!=
EFFECT_TONE_PORTAMENTO
&&
(
effect
>>
8
)
!=
EFFECT_VOLSLIDE_TONEPORTA
)
)
{
if
(
period
!=
0
)
cptr
->
samppos
=
0
;
}
cptr
->
decalperiod
=
0
;
if
(
period
)
{
if
(
cptr
->
finetune
)
{
if
(
cptr
->
finetune
<=
7
)
{
period
=
mod
->
fullperiod
[
getnote
(
mod
,
period
,
0
)
+
cptr
->
finetune
];
}
else
{
period
=
mod
->
fullperiod
[
getnote
(
mod
,
period
,
0
)
-
(
16
-
(
cptr
->
finetune
))
];
}
}
cptr
->
period
=
period
;
}
}
cptr
->
effect
=
0
;
cptr
->
parameffect
=
0
;
cptr
->
effect_code
=
effect
;
switch
(
effect
>>
8
)
{
case
EFFECT_ARPEGGIO
:
/*
[0]: Arpeggio
Where [0][x][y] means "play note, note+x semitones, note+y
semitones, then return to original note". The fluctuations are
carried out evenly spaced in one pattern division. They are usually
used to simulate chords, but this doesn't work too well. They are
also used to produce heavy vibrato. A major chord is when x=4, y=7.
A minor chord is when x=3, y=7.
*/
if
(
effect
&
0xff
)
{
cptr
->
effect
=
EFFECT_ARPEGGIO
;
cptr
->
parameffect
=
effect
&
0xff
;
cptr
->
ArpIndex
=
0
;
curnote
=
getnote
(
mod
,
cptr
->
period
,
cptr
->
finetune
);
cptr
->
Arpperiods
[
0
]
=
cptr
->
period
;
arpnote
=
curnote
+
(((
cptr
->
parameffect
>>
4
)
&
0xF
)
*
8
);
if
(
arpnote
>=
FULL_PERIOD_TABLE_LENGTH
)
arpnote
=
FULL_PERIOD_TABLE_LENGTH
-
1
;
cptr
->
Arpperiods
[
1
]
=
mod
->
fullperiod
[
arpnote
];
arpnote
=
curnote
+
(((
cptr
->
parameffect
)
&
0xF
)
*
8
);
if
(
arpnote
>=
FULL_PERIOD_TABLE_LENGTH
)
arpnote
=
FULL_PERIOD_TABLE_LENGTH
-
1
;
cptr
->
Arpperiods
[
2
]
=
mod
->
fullperiod
[
arpnote
];
}
break
;
case
EFFECT_PORTAMENTO_UP
:
/*
[1]: Slide up
Where [1][x][y] means "smoothly decrease the period of current
sample by x*16+y after each tick in the division". The
ticks/division are set with the 'set speed' effect (see below). If
the period of the note being played is z, then the final period
will be z - (x*16 + y)*(ticks - 1). As the slide rate depends on
the speed, changing the speed will change the slide. You cannot
slide beyond the note B3 (period 113).
*/
cptr
->
effect
=
EFFECT_PORTAMENTO_UP
;
cptr
->
parameffect
=
effect
&
0xff
;
break
;
case
EFFECT_PORTAMENTO_DOWN
:
/*
[2]: Slide down
Where [2][x][y] means "smoothly increase the period of current
sample by x*16+y after each tick in the division". Similar to [1],
but lowers the pitch. You cannot slide beyond the note C1 (period
856).
*/
cptr
->
effect
=
EFFECT_PORTAMENTO_DOWN
;
cptr
->
parameffect
=
effect
&
0xff
;
break
;
case
EFFECT_TONE_PORTAMENTO
:
/*
[3]: Slide to note
Where [3][x][y] means "smoothly change the period of current sample
by x*16+y after each tick in the division, never sliding beyond
current period". The period-length in this channel's division is a
parameter to this effect, and hence is not played. Sliding to a
note is similar to effects [1] and [2], but the slide will not go
beyond the given period, and the direction is implied by that
period. If x and y are both 0, then the old slide will continue.
*/
cptr
->
effect
=
EFFECT_TONE_PORTAMENTO
;
if
(
(
effect
&
0xff
)
!=
0
)
{
cptr
->
portaspeed
=
(
short
)(
effect
&
0xff
);
}
if
(
period
!=
0
)
{
cptr
->
portaperiod
=
period
;
cptr
->
period
=
operiod
;
}
break
;
case
EFFECT_VIBRATO
:
/*
[4]: Vibrato
Where [4][x][y] means "oscillate the sample pitch using a
particular waveform with amplitude y/16 semitones, such that (x *
ticks)/64 cycles occur in the division". The waveform is set using
effect [14][4]. By placing vibrato effects on consecutive
divisions, the vibrato effect can be maintained. If either x or y
are 0, then the old vibrato values will be used.
*/
cptr
->
effect
=
EFFECT_VIBRATO
;
if
(
(
effect
&
0x0F
)
!=
0
)
// Depth continue or change ?
cptr
->
vibraparam
=
(
cptr
->
vibraparam
&
0xF0
)
|
(
effect
&
0x0F
);
if
(
(
effect
&
0xF0
)
!=
0
)
// Speed continue or change ?
cptr
->
vibraparam
=
(
cptr
->
vibraparam
&
0x0F
)
|
(
effect
&
0xF0
);
break
;
case
EFFECT_VOLSLIDE_TONEPORTA
:
/*
[5]: Continue 'Slide to note', but also do Volume slide
Where [5][x][y] means "either slide the volume up x*(ticks - 1) or
slide the volume down y*(ticks - 1), at the same time as continuing
the last 'Slide to note'". It is illegal for both x and y to be
non-zero. You cannot slide outside the volume range 0..64. The
period-length in this channel's division is a parameter to this
effect, and hence is not played.
*/
if
(
period
!=
0
)
{
cptr
->
portaperiod
=
period
;
cptr
->
period
=
operiod
;
}
cptr
->
effect
=
EFFECT_VOLSLIDE_TONEPORTA
;
if
(
(
effect
&
0xFF
)
!=
0
)
cptr
->
volumeslide
=
(
effect
&
0xFF
);
break
;
case
EFFECT_VOLSLIDE_VIBRATO
:
/*
[6]: Continue 'Vibrato', but also do Volume slide
Where [6][x][y] means "either slide the volume up x*(ticks - 1) or
slide the volume down y*(ticks - 1), at the same time as continuing
the last 'Vibrato'". It is illegal for both x and y to be non-zero.
You cannot slide outside the volume range 0..64.
*/
cptr
->
effect
=
EFFECT_VOLSLIDE_VIBRATO
;
if
(
(
effect
&
0xFF
)
!=
0
)
cptr
->
volumeslide
=
(
effect
&
0xFF
);
break
;
case
EFFECT_SET_OFFSET
:
/*
[9]: Set sample offset
Where [9][x][y] means "play the sample from offset x*4096 + y*256".
The offset is measured in words. If no sample is given, yet one is
still playing on this channel, it should be retriggered to the new
offset using the current volume.
*/
cptr
->
samppos
=
((
effect
>>
4
)
*
4096
)
+
((
effect
&
0xF
)
*
256
);
break
;
case
EFFECT_VOLUME_SLIDE
:
/*
[10]: Volume slide
Where [10][x][y] means "either slide the volume up x*(ticks - 1) or
slide the volume down y*(ticks - 1)". If both x and y are non-zero,
then the y value is ignored (assumed to be 0). You cannot slide
outside the volume range 0..64.
*/
cptr
->
effect
=
EFFECT_VOLUME_SLIDE
;
cptr
->
volumeslide
=
(
effect
&
0xFF
);
break
;
case
EFFECT_JUMP_POSITION
:
/*
[11]: Position Jump
Where [11][x][y] means "stop the pattern after this division, and
continue the song at song-position x*16+y". This shifts the
'pattern-cursor' in the pattern table (see above). Legal values for
x*16+y are from 0 to 127.
*/
mod
->
tablepos
=
(
effect
&
0xFF
);
if
(
mod
->
tablepos
>=
mod
->
song
.
length
)
{
mod
->
tablepos
=
0
;
}
mod
->
patternpos
=
0
;
mod
->
jump_loop_effect
=
1
;
break
;
case
EFFECT_SET_VOLUME
:
/*
[12]: Set volume
Where [12][x][y] means "set current sample's volume to x*16+y".
Legal volumes are 0..64.
*/
cptr
->
volume
=
(
effect
&
0xFF
);
break
;
case
EFFECT_PATTERN_BREAK
:
/*
[13]: Pattern Break
Where [13][x][y] means "stop the pattern after this division, and
continue the song at the next pattern at division x*10+y" (the 10
is not a typo). Legal divisions are from 0 to 63 (note Protracker
exception above).
*/
mod
->
patternpos
=
(
((
effect
>>
4
)
&
0xF
)
*
10
+
(
effect
&
0xF
)
)
*
mod
->
number_of_channels
;
mod
->
jump_loop_effect
=
1
;
mod
->
tablepos
++
;
if
(
mod
->
tablepos
>=
mod
->
song
.
length
)
{
mod
->
tablepos
=
0
;
}
break
;
case
EFFECT_EXTENDED
:
switch
(
(
effect
>>
4
)
&
0xF
)
{
case
EFFECT_E_FINE_PORTA_UP
:
/*
[14][1]: Fineslide up
Where [14][1][x] means "decrement the period of the current sample
by x". The incrementing takes place at the beginning of the
division, and hence there is no actual sliding. You cannot slide
beyond the note B3 (period 113).
*/
cptr
->
period
-=
(
effect
&
0xF
);
if
(
cptr
->
period
<
113
)
cptr
->
period
=
113
;
break
;
case
EFFECT_E_FINE_PORTA_DOWN
:
/*
[14][2]: Fineslide down
Where [14][2][x] means "increment the period of the current sample
by x". Similar to [14][1] but shifts the pitch down. You cannot
slide beyond the note C1 (period 856).
*/
cptr
->
period
+=
(
effect
&
0xF
);
if
(
cptr
->
period
>
856
)
cptr
->
period
=
856
;
break
;
case
EFFECT_E_FINE_VOLSLIDE_UP
:
/*
[14][10]: Fine volume slide up
Where [14][10][x] means "increment the volume of the current sample
by x". The incrementing takes place at the beginning of the
division, and hence there is no sliding. You cannot slide beyond
volume 64.
*/
cptr
->
volume
+=
(
effect
&
0xF
);
if
(
cptr
->
volume
>
64
)
cptr
->
volume
=
64
;
break
;
case
EFFECT_E_FINE_VOLSLIDE_DOWN
:
/*
[14][11]: Fine volume slide down
Where [14][11][x] means "decrement the volume of the current sample
by x". Similar to [14][10] but lowers volume. You cannot slide
beyond volume 0.
*/
cptr
->
volume
-=
(
effect
&
0xF
);
if
(
cptr
->
volume
>
200
)
cptr
->
volume
=
0
;
break
;
case
EFFECT_E_PATTERN_LOOP
:
/*
[14][6]: Loop pattern
Where [14][6][x] means "set the start of a loop to this division if
x is 0, otherwise after this division, jump back to the start of a
loop and play it another x times before continuing". If the start
of the loop was not set, it will default to the start of the
current pattern. Hence 'loop pattern' cannot be performed across
multiple patterns. Note that loops do not support nesting, and you
may generate an infinite loop if you try to nest 'loop pattern's.
*/
if
(
effect
&
0xF
)
{
if
(
cptr
->
patternloopcnt
)
{
cptr
->
patternloopcnt
--
;
if
(
cptr
->
patternloopcnt
)
{
mod
->
patternpos
=
cptr
->
patternloopstartpoint
;
mod
->
jump_loop_effect
=
1
;
}
else
{
cptr
->
patternloopstartpoint
=
mod
->
patternpos
;
}
}
else
{
cptr
->
patternloopcnt
=
(
effect
&
0xF
);
mod
->
patternpos
=
cptr
->
patternloopstartpoint
;
mod
->
jump_loop_effect
=
1
;
}
}
else
// Start point
{
cptr
->
patternloopstartpoint
=
mod
->
patternpos
;
}
break
;
case
EFFECT_E_PATTERN_DELAY
:
/*
[14][14]: Delay pattern
Where [14][14][x] means "after this division there will be a delay
equivalent to the time taken to play x divisions after which the
pattern will be resumed". The delay only relates to the
interpreting of new divisions, and all effects and previous notes
continue during delay.
*/
mod
->
patterndelay
=
(
effect
&
0xF
);
break
;
case
EFFECT_E_NOTE_CUT
:
/*
[14][12]: Cut sample
Where [14][12][x] means "after the current sample has been played
for x ticks in this division, its volume will be set to 0". This
implies that if x is 0, then you will not hear any of the sample.
If you wish to insert "silence" in a pattern, it is better to use a
"silence"-sample (see above) due to the lack of proper support for
this effect.
*/
cptr
->
effect
=
EFFECT_E_NOTE_CUT
;
cptr
->
cut_param
=
(
effect
&
0xF
);
if
(
!
cptr
->
cut_param
)
cptr
->
volume
=
0
;
break
;
default:
break
;
}
break
;
case
0xF
:
/*
[15]: Set speed
Where [15][x][y] means "set speed to x*16+y". Though it is nowhere
near that simple. Let z = x*16+y. Depending on what values z takes,
different units of speed are set, there being two: ticks/division
and beats/minute (though this one is only a label and not strictly
true). If z=0, then what should technically happen is that the
module stops, but in practice it is treated as if z=1, because
there is already a method for stopping the module (running out of
patterns). If z<=32, then it means "set ticks/division to z"
otherwise it means "set beats/minute to z" (convention says that
this should read "If z<32.." but there are some composers out there
that defy conventions). Default values are 6 ticks/division, and
125 beats/minute (4 divisions = 1 beat). The beats/minute tag is
only meaningful for 6 ticks/division. To get a more accurate view
of how things work, use the following formula:
24 * beats/minute
divisions/minute = -----------------
ticks/division
Hence divisions/minute range from 24.75 to 6120, eg. to get a value
of 2000 divisions/minute use 3 ticks/division and 250 beats/minute.
If multiple "set speed" effects are performed in a single division,
the ones on higher-numbered channels take precedence over the ones
on lower-numbered channels. This effect has a large number of
different implementations, but the one described here has the
widest usage.
*/
if
(
(
effect
&
0xFF
)
<
0x21
)
{
if
(
effect
&
0xFF
)
{
mod
->
song
.
speed
=
effect
&
0xFF
;
mod
->
patternticksaim
=
(
long
)
mod
->
song
.
speed
*
((
mod
->
playrate
*
5
)
/
(((
long
)
2
*
(
long
)
mod
->
bpm
)));
}
}
if
(
(
effect
&
0xFF
)
>=
0x21
)
{
/// HZ = 2 * BPM / 5
mod
->
bpm
=
effect
&
0xFF
;
mod
->
patternticksaim
=
(
long
)
mod
->
song
.
speed
*
((
mod
->
playrate
*
5
)
/
(((
long
)
2
*
(
long
)
mod
->
bpm
)));
}
break
;
default:
// Unsupported effect
break
;
}
}
static
void
workeffect
(
note
*
nptr
,
channel
*
cptr
)
{
switch
(
cptr
->
effect
)
{
case
EFFECT_ARPEGGIO
:
if
(
cptr
->
parameffect
)
{
cptr
->
decalperiod
=
cptr
->
period
-
cptr
->
Arpperiods
[
cptr
->
ArpIndex
];
cptr
->
ArpIndex
++
;
if
(
cptr
->
ArpIndex
>
2
)
cptr
->
ArpIndex
=
0
;
}
break
;
case
EFFECT_PORTAMENTO_UP
:
if
(
cptr
->
period
)
{
cptr
->
period
-=
cptr
->
parameffect
;
if
(
cptr
->
period
<
113
||
cptr
->
period
>
20000
)
cptr
->
period
=
113
;
}
break
;
case
EFFECT_PORTAMENTO_DOWN
:
if
(
cptr
->
period
)
{
cptr
->
period
+=
cptr
->
parameffect
;
if
(
cptr
->
period
>
20000
)
cptr
->
period
=
20000
;
}
break
;
case
EFFECT_VOLSLIDE_TONEPORTA
:
case
EFFECT_TONE_PORTAMENTO
:
if
(
cptr
->
period
&&
(
cptr
->
period
!=
cptr
->
portaperiod
)
&&
cptr
->
portaperiod
)
{
if
(
cptr
->
period
>
cptr
->
portaperiod
)
{
if
(
cptr
->
period
-
cptr
->
portaperiod
>=
cptr
->
portaspeed
)
{
cptr
->
period
-=
cptr
->
portaspeed
;
}
else
{
cptr
->
period
=
cptr
->
portaperiod
;
}
}
else
{
if
(
cptr
->
portaperiod
-
cptr
->
period
>=
cptr
->
portaspeed
)
{
cptr
->
period
+=
cptr
->
portaspeed
;
}
else
{
cptr
->
period
=
cptr
->
portaperiod
;
}
}
if
(
cptr
->
period
==
cptr
->
portaperiod
)
{
// If the slide is over, don't let it to be retriggered.
cptr
->
portaperiod
=
0
;
}
}
if
(
cptr
->
effect
==
EFFECT_VOLSLIDE_TONEPORTA
)
{
if
(
cptr
->
volumeslide
>
0x0F
)
{
cptr
->
volume
=
cptr
->
volume
+
(
cptr
->
volumeslide
>>
4
);
if
(
cptr
->
volume
>
63
)
cptr
->
volume
=
63
;
}
else
{
cptr
->
volume
=
cptr
->
volume
-
(
cptr
->
volumeslide
);
if
(
cptr
->
volume
>
63
)
cptr
->
volume
=
0
;
}
}
break
;
case
EFFECT_VOLSLIDE_VIBRATO
:
case
EFFECT_VIBRATO
:
cptr
->
vibraperiod
=
(
(
cptr
->
vibraparam
&
0xF
)
*
sintable
[
cptr
->
vibrapointeur
&
0x1F
]
)
>>
7
;
if
(
cptr
->
vibrapointeur
>
31
)
cptr
->
vibraperiod
=
-
cptr
->
vibraperiod
;
cptr
->
vibrapointeur
=
(
cptr
->
vibrapointeur
+
(((
cptr
->
vibraparam
>>
4
))
&
0xf
))
&
0x3F
;
if
(
cptr
->
effect
==
EFFECT_VOLSLIDE_VIBRATO
)
{
if
(
cptr
->
volumeslide
>
0xF
)
{
cptr
->
volume
=
cptr
->
volume
+
(
cptr
->
volumeslide
>>
4
);
if
(
cptr
->
volume
>
64
)
cptr
->
volume
=
64
;
}
else
{
cptr
->
volume
=
cptr
->
volume
-
cptr
->
volumeslide
;
if
(
cptr
->
volume
>
64
)
cptr
->
volume
=
0
;
}
}
break
;
case
EFFECT_VOLUME_SLIDE
:
if
(
cptr
->
volumeslide
>
0xF
)
{
cptr
->
volume
+=
(
cptr
->
volumeslide
>>
4
);
if
(
cptr
->
volume
>
64
)
cptr
->
volume
=
64
;
}
else
{
cptr
->
volume
-=
(
cptr
->
volumeslide
&
0xf
);
if
(
cptr
->
volume
>
64
)
cptr
->
volume
=
0
;
}
break
;
case
EFFECT_E_NOTE_CUT
:
if
(
cptr
->
cut_param
)
cptr
->
cut_param
--
;
if
(
!
cptr
->
cut_param
)
cptr
->
volume
=
0
;
break
;
default:
break
;
}
}
///////////////////////////////////////////////////////////////////////////////////
bool
jar_mod_init
(
jar_mod_context_t
*
modctx
)
{
muint
i
,
j
;
if
(
modctx
)
{
memclear
(
modctx
,
0
,
sizeof
(
jar_mod_context_t
));
modctx
->
playrate
=
DEFAULT_SAMPLE_RATE
;
modctx
->
stereo
=
1
;
modctx
->
stereo_separation
=
1
;
modctx
->
bits
=
16
;
modctx
->
filter
=
1
;
for
(
i
=
0
;
i
<
PERIOD_TABLE_LENGTH
-
1
;
i
++
)
{
for
(
j
=
0
;
j
<
8
;
j
++
)
{
modctx
->
fullperiod
[(
i
*
8
)
+
j
]
=
periodtable
[
i
]
-
(((
periodtable
[
i
]
-
periodtable
[
i
+
1
]
)
/
8
)
*
j
);
}
}
return
1
;
}
return
0
;
}
bool
jar_mod_setcfg
(
jar_mod_context_t
*
modctx
,
int
samplerate
,
int
bits
,
int
stereo
,
int
stereo_separation
,
int
filter
)
{
if
(
modctx
)
{
modctx
->
playrate
=
samplerate
;
if
(
stereo
)
modctx
->
stereo
=
1
;
else
modctx
->
stereo
=
0
;
if
(
stereo_separation
<
4
)
{
modctx
->
stereo_separation
=
stereo_separation
;
}
if
(
bits
==
8
||
bits
==
16
)
modctx
->
bits
=
bits
;
else
modctx
->
bits
=
16
;
if
(
filter
)
modctx
->
filter
=
1
;
else
modctx
->
filter
=
0
;
return
1
;
}
return
0
;
}
// make certain that mod_data stays in memory while playing
static
bool
jar_mod_load
(
jar_mod_context_t
*
modctx
,
void
*
mod_data
,
int
mod_data_size
)
{
muint
i
,
max
;
unsigned
short
t
;
sample
*
sptr
;
unsigned
char
*
modmemory
,
*
endmodmemory
;
modmemory
=
(
unsigned
char
*
)
mod_data
;
endmodmemory
=
modmemory
+
mod_data_size
;
if
(
modmemory
)
{
if
(
modctx
)
{
memcopy
(
&
(
modctx
->
song
.
title
),
modmemory
,
1084
);
i
=
0
;
modctx
->
number_of_channels
=
0
;
while
(
modlist
[
i
].
numberofchannels
)
{
if
(
memcompare
(
modctx
->
song
.
signature
,
modlist
[
i
].
signature
,
4
))
{
modctx
->
number_of_channels
=
modlist
[
i
].
numberofchannels
;
}
i
++
;
}
if
(
!
modctx
->
number_of_channels
)
{
// 15 Samples modules support
// Shift the whole datas to make it look likes a standard 4 channels mod.
memcopy
(
&
(
modctx
->
song
.
signature
),
"M.K."
,
4
);
memcopy
(
&
(
modctx
->
song
.
length
),
&
(
modctx
->
song
.
samples
[
15
]),
130
);
memclear
(
&
(
modctx
->
song
.
samples
[
15
]),
0
,
480
);
modmemory
+=
600
;
modctx
->
number_of_channels
=
4
;
}
else
{
modmemory
+=
1084
;
}
if
(
modmemory
>=
endmodmemory
)
return
0
;
// End passed ? - Probably a bad file !
// Patterns loading
for
(
i
=
max
=
0
;
i
<
128
;
i
++
)
{
while
(
max
<=
modctx
->
song
.
patterntable
[
i
])
{
modctx
->
patterndata
[
max
]
=
(
note
*
)
modmemory
;
modmemory
+=
(
256
*
modctx
->
number_of_channels
);
max
++
;
if
(
modmemory
>=
endmodmemory
)
return
0
;
// End passed ? - Probably a bad file !
}
}
for
(
i
=
0
;
i
<
31
;
i
++
)
modctx
->
sampledata
[
i
]
=
0
;
// Samples loading
for
(
i
=
0
,
sptr
=
modctx
->
song
.
samples
;
i
<
31
;
i
++
,
sptr
++
)
{
t
=
(
sptr
->
length
&
0xFF00
)
>>
8
|
(
sptr
->
length
&
0xFF
)
<<
8
;
sptr
->
length
=
t
*
2
;
t
=
(
sptr
->
reppnt
&
0xFF00
)
>>
8
|
(
sptr
->
reppnt
&
0xFF
)
<<
8
;
sptr
->
reppnt
=
t
*
2
;
t
=
(
sptr
->
replen
&
0xFF00
)
>>
8
|
(
sptr
->
replen
&
0xFF
)
<<
8
;
sptr
->
replen
=
t
*
2
;
if
(
sptr
->
length
==
0
)
continue
;
modctx
->
sampledata
[
i
]
=
(
char
*
)
modmemory
;
modmemory
+=
sptr
->
length
;
if
(
sptr
->
replen
+
sptr
->
reppnt
>
sptr
->
length
)
sptr
->
replen
=
sptr
->
length
-
sptr
->
reppnt
;
if
(
modmemory
>
endmodmemory
)
return
0
;
// End passed ? - Probably a bad file !
}
// States init
modctx
->
tablepos
=
0
;
modctx
->
patternpos
=
0
;
modctx
->
song
.
speed
=
6
;
modctx
->
bpm
=
125
;
modctx
->
samplenb
=
0
;
modctx
->
patternticks
=
(((
long
)
modctx
->
song
.
speed
*
modctx
->
playrate
*
5
)
/
(
2
*
modctx
->
bpm
))
+
1
;
modctx
->
patternticksaim
=
((
long
)
modctx
->
song
.
speed
*
modctx
->
playrate
*
5
)
/
(
2
*
modctx
->
bpm
);
modctx
->
sampleticksconst
=
3546894UL
/
modctx
->
playrate
;
//8448*428/playrate;
for
(
i
=
0
;
i
<
modctx
->
number_of_channels
;
i
++
)
{
modctx
->
channels
[
i
].
volume
=
0
;
modctx
->
channels
[
i
].
period
=
0
;
}
modctx
->
mod_loaded
=
1
;
return
1
;
}
}
return
0
;
}
void
jar_mod_fillbuffer
(
jar_mod_context_t
*
modctx
,
short
*
outbuffer
,
unsigned
long
nbsample
,
jar_mod_tracker_buffer_state
*
trkbuf
)
{
unsigned
long
i
,
j
;
unsigned
long
k
;
unsigned
char
c
;
unsigned
int
state_remaining_steps
;
int
l
,
r
;
int
ll
,
lr
;
int
tl
,
tr
;
short
finalperiod
;
note
*
nptr
;
channel
*
cptr
;
if
(
modctx
&&
outbuffer
)
{
if
(
modctx
->
mod_loaded
)
{
state_remaining_steps
=
0
;
if
(
trkbuf
)
{
trkbuf
->
cur_rd_index
=
0
;
memcopy
(
trkbuf
->
name
,
modctx
->
song
.
title
,
sizeof
(
modctx
->
song
.
title
));
for
(
i
=
0
;
i
<
31
;
i
++
)
{
memcopy
(
trkbuf
->
instruments
[
i
].
name
,
modctx
->
song
.
samples
[
i
].
name
,
sizeof
(
trkbuf
->
instruments
[
i
].
name
));
}
}
ll
=
modctx
->
last_l_sample
;
lr
=
modctx
->
last_r_sample
;
for
(
i
=
0
;
i
<
nbsample
;
i
++
)
{
//---------------------------------------
if
(
modctx
->
patternticks
++
>
modctx
->
patternticksaim
)
{
if
(
!
modctx
->
patterndelay
)
{
nptr
=
modctx
->
patterndata
[
modctx
->
song
.
patterntable
[
modctx
->
tablepos
]];
nptr
=
nptr
+
modctx
->
patternpos
;
cptr
=
modctx
->
channels
;
modctx
->
patternticks
=
0
;
modctx
->
patterntickse
=
0
;
for
(
c
=
0
;
c
<
modctx
->
number_of_channels
;
c
++
)
{
worknote
((
note
*
)(
nptr
+
c
),
(
channel
*
)(
cptr
+
c
),(
char
)(
c
+
1
),
modctx
);
}
if
(
!
modctx
->
jump_loop_effect
)
modctx
->
patternpos
+=
modctx
->
number_of_channels
;
else
modctx
->
jump_loop_effect
=
0
;
if
(
modctx
->
patternpos
==
64
*
modctx
->
number_of_channels
)
{
modctx
->
tablepos
++
;
modctx
->
patternpos
=
0
;
if
(
modctx
->
tablepos
>=
modctx
->
song
.
length
)
{
modctx
->
tablepos
=
0
;
modctx
->
loopcount
++
;
// count next loop
}
}
}
else
{
modctx
->
patterndelay
--
;
modctx
->
patternticks
=
0
;
modctx
->
patterntickse
=
0
;
}
}
if
(
modctx
->
patterntickse
++
>
(
modctx
->
patternticksaim
/
modctx
->
song
.
speed
)
)
{
nptr
=
modctx
->
patterndata
[
modctx
->
song
.
patterntable
[
modctx
->
tablepos
]];
nptr
=
nptr
+
modctx
->
patternpos
;
cptr
=
modctx
->
channels
;
for
(
c
=
0
;
c
<
modctx
->
number_of_channels
;
c
++
)
{
workeffect
(
nptr
+
c
,
cptr
+
c
);
}
modctx
->
patterntickse
=
0
;
}
//---------------------------------------
if
(
trkbuf
&&
!
state_remaining_steps
)
{
if
(
trkbuf
->
nb_of_state
<
trkbuf
->
nb_max_of_state
)
{
memclear
(
&
trkbuf
->
track_state_buf
[
trkbuf
->
nb_of_state
],
0
,
sizeof
(
tracker_state
));
}
}
l
=
0
;
r
=
0
;
for
(
j
=
0
,
cptr
=
modctx
->
channels
;
j
<
modctx
->
number_of_channels
;
j
++
,
cptr
++
)
{
if
(
cptr
->
period
!=
0
)
{
finalperiod
=
cptr
->
period
-
cptr
->
decalperiod
-
cptr
->
vibraperiod
;
if
(
finalperiod
)
{
cptr
->
samppos
+=
(
(
modctx
->
sampleticksconst
<<
10
)
/
finalperiod
);
}
cptr
->
ticks
++
;
if
(
cptr
->
replen
<=
2
)
{
if
(
(
cptr
->
samppos
>>
10
)
>=
(
cptr
->
length
)
)
{
cptr
->
length
=
0
;
cptr
->
reppnt
=
0
;
if
(
cptr
->
length
)
cptr
->
samppos
=
cptr
->
samppos
%
(((
unsigned
long
)
cptr
->
length
)
<<
10
);
else
cptr
->
samppos
=
0
;
}
}
else
{
if
(
(
cptr
->
samppos
>>
10
)
>=
(
unsigned
long
)(
cptr
->
replen
+
cptr
->
reppnt
)
)
{
cptr
->
samppos
=
((
unsigned
long
)(
cptr
->
reppnt
)
<<
10
)
+
(
cptr
->
samppos
%
((
unsigned
long
)(
cptr
->
replen
+
cptr
->
reppnt
)
<<
10
));
}
}
k
=
cptr
->
samppos
>>
10
;
if
(
cptr
->
sampdata
!=
0
&&
(
((
j
&
3
)
==
1
)
||
((
j
&
3
)
==
2
)
)
)
{
r
+=
(
cptr
->
sampdata
[
k
]
*
cptr
->
volume
);
}
if
(
cptr
->
sampdata
!=
0
&&
(
((
j
&
3
)
==
0
)
||
((
j
&
3
)
==
3
)
)
)
{
l
+=
(
cptr
->
sampdata
[
k
]
*
cptr
->
volume
);
}
if
(
trkbuf
&&
!
state_remaining_steps
)
{
if
(
trkbuf
->
nb_of_state
<
trkbuf
->
nb_max_of_state
)
{
trkbuf
->
track_state_buf
[
trkbuf
->
nb_of_state
].
number_of_tracks
=
modctx
->
number_of_channels
;
trkbuf
->
track_state_buf
[
trkbuf
->
nb_of_state
].
buf_index
=
i
;
trkbuf
->
track_state_buf
[
trkbuf
->
nb_of_state
].
cur_pattern
=
modctx
->
song
.
patterntable
[
modctx
->
tablepos
];
trkbuf
->
track_state_buf
[
trkbuf
->
nb_of_state
].
cur_pattern_pos
=
modctx
->
patternpos
/
modctx
->
number_of_channels
;
trkbuf
->
track_state_buf
[
trkbuf
->
nb_of_state
].
cur_pattern_table_pos
=
modctx
->
tablepos
;
trkbuf
->
track_state_buf
[
trkbuf
->
nb_of_state
].
bpm
=
modctx
->
bpm
;
trkbuf
->
track_state_buf
[
trkbuf
->
nb_of_state
].
speed
=
modctx
->
song
.
speed
;
trkbuf
->
track_state_buf
[
trkbuf
->
nb_of_state
].
tracks
[
j
].
cur_effect
=
cptr
->
effect_code
;
trkbuf
->
track_state_buf
[
trkbuf
->
nb_of_state
].
tracks
[
j
].
cur_parameffect
=
cptr
->
parameffect
;
trkbuf
->
track_state_buf
[
trkbuf
->
nb_of_state
].
tracks
[
j
].
cur_period
=
finalperiod
;
trkbuf
->
track_state_buf
[
trkbuf
->
nb_of_state
].
tracks
[
j
].
cur_volume
=
cptr
->
volume
;
trkbuf
->
track_state_buf
[
trkbuf
->
nb_of_state
].
tracks
[
j
].
instrument_number
=
(
unsigned
char
)
cptr
->
sampnum
;
}
}
}
}
if
(
trkbuf
&&
!
state_remaining_steps
)
{
state_remaining_steps
=
trkbuf
->
sample_step
;
if
(
trkbuf
->
nb_of_state
<
trkbuf
->
nb_max_of_state
)
trkbuf
->
nb_of_state
++
;
}
else
{
state_remaining_steps
--
;
}
tl
=
(
short
)
l
;
tr
=
(
short
)
r
;
if
(
modctx
->
filter
)
{
// Filter
l
=
(
l
+
ll
)
>>
1
;
r
=
(
r
+
lr
)
>>
1
;
}
if
(
modctx
->
stereo_separation
==
1
)
{
// Left & Right Stereo panning
l
=
(
l
+
(
r
>>
1
));
r
=
(
r
+
(
l
>>
1
));
}
// Level limitation
if
(
l
>
32767
)
l
=
32767
;
if
(
l
<
-
32768
)
l
=
-
32768
;
if
(
r
>
32767
)
r
=
32767
;
if
(
r
<
-
32768
)
r
=
-
32768
;
// Store the final sample.
outbuffer
[(
i
*
2
)]
=
l
;
outbuffer
[(
i
*
2
)
+
1
]
=
r
;
ll
=
tl
;
lr
=
tr
;
}
modctx
->
last_l_sample
=
ll
;
modctx
->
last_r_sample
=
lr
;
modctx
->
samplenb
=
modctx
->
samplenb
+
nbsample
;
}
else
{
for
(
i
=
0
;
i
<
nbsample
;
i
++
)
{
// Mod not loaded. Return blank buffer.
outbuffer
[(
i
*
2
)]
=
0
;
outbuffer
[(
i
*
2
)
+
1
]
=
0
;
}
if
(
trkbuf
)
{
trkbuf
->
nb_of_state
=
0
;
trkbuf
->
cur_rd_index
=
0
;
trkbuf
->
name
[
0
]
=
0
;
memclear
(
trkbuf
->
track_state_buf
,
0
,
sizeof
(
tracker_state
)
*
trkbuf
->
nb_max_of_state
);
memclear
(
trkbuf
->
instruments
,
0
,
sizeof
(
trkbuf
->
instruments
));
}
}
}
}
//resets internals for mod context
static
bool
jar_mod_reset
(
jar_mod_context_t
*
modctx
)
{
if
(
modctx
)
{
memclear
(
&
modctx
->
song
,
0
,
sizeof
(
modctx
->
song
));
memclear
(
&
modctx
->
sampledata
,
0
,
sizeof
(
modctx
->
sampledata
));
memclear
(
&
modctx
->
patterndata
,
0
,
sizeof
(
modctx
->
patterndata
));
modctx
->
tablepos
=
0
;
modctx
->
patternpos
=
0
;
modctx
->
patterndelay
=
0
;
modctx
->
jump_loop_effect
=
0
;
modctx
->
bpm
=
0
;
modctx
->
patternticks
=
0
;
modctx
->
patterntickse
=
0
;
modctx
->
patternticksaim
=
0
;
modctx
->
sampleticksconst
=
0
;
modctx
->
samplenb
=
0
;
memclear
(
modctx
->
channels
,
0
,
sizeof
(
modctx
->
channels
));
modctx
->
number_of_channels
=
0
;
modctx
->
mod_loaded
=
0
;
modctx
->
last_r_sample
=
0
;
modctx
->
last_l_sample
=
0
;
return
jar_mod_init
(
modctx
);
}
return
0
;
}
void
jar_mod_unload
(
jar_mod_context_t
*
modctx
)
{
if
(
modctx
)
{
if
(
modctx
->
modfile
)
{
free
(
modctx
->
modfile
);
modctx
->
modfile
=
0
;
modctx
->
modfilesize
=
0
;
modctx
->
loopcount
=
0
;
}
jar_mod_reset
(
modctx
);
}
}
mulong
jar_mod_load_file
(
jar_mod_context_t
*
modctx
,
const
char
*
filename
)
{
mulong
fsize
=
0
;
if
(
modctx
->
modfile
)
{
free
(
modctx
->
modfile
);
modctx
->
modfile
=
0
;
}
FILE
*
f
=
fopen
(
filename
,
"rb"
);
if
(
f
)
{
fseek
(
f
,
0
,
SEEK_END
);
fsize
=
ftell
(
f
);
fseek
(
f
,
0
,
SEEK_SET
);
if
(
fsize
&&
fsize
<
32
*
1024
*
1024
)
{
modctx
->
modfile
=
malloc
(
fsize
);
modctx
->
modfilesize
=
fsize
;
memset
(
modctx
->
modfile
,
0
,
fsize
);
fread
(
modctx
->
modfile
,
fsize
,
1
,
f
);
fclose
(
f
);
if
(
!
jar_mod_load
(
modctx
,
(
void
*
)
modctx
->
modfile
,
fsize
))
fsize
=
0
;
}
else
fsize
=
0
;
}
return
fsize
;
}
mulong
jar_mod_current_samples
(
jar_mod_context_t
*
modctx
)
{
if
(
modctx
)
return
modctx
->
samplenb
;
return
0
;
}
// Works, however it is very slow, this data should be cached to ensure it is run only once per file
mulong
jar_mod_max_samples
(
jar_mod_context_t
*
ctx
)
{
mint
buff
[
2
];
mulong
len
;
mulong
lastcount
=
ctx
->
loopcount
;
while
(
ctx
->
loopcount
<=
lastcount
)
jar_mod_fillbuffer
(
ctx
,
buff
,
1
,
0
);
len
=
ctx
->
samplenb
;
jar_mod_seek_start
(
ctx
);
return
len
;
}
// move seek_val to sample index, 0 -> jar_mod_max_samples is the range
void
jar_mod_seek_start
(
jar_mod_context_t
*
ctx
)
{
if
(
ctx
&&
ctx
->
modfile
)
{
muchar
*
ftmp
=
ctx
->
modfile
;
mulong
stmp
=
ctx
->
modfilesize
;
muint
lcnt
=
ctx
->
loopcount
;
if
(
jar_mod_reset
(
ctx
)){
jar_mod_load
(
ctx
,
ftmp
,
stmp
);
ctx
->
modfile
=
ftmp
;
ctx
->
modfilesize
=
stmp
;
ctx
->
loopcount
=
lcnt
;
}
}
}
#endif // end of JAR_MOD_IMPLEMENTATION
//-------------------------------------------------------------------------------
#endif //end of header file
\ No newline at end of file
extras/jar_xm.h
0 → 100644
View file @
37491c1f
// jar_xm.h - v0.01 - public domain - Joshua Reisenauer, MAR 2016
//
// HISTORY:
//
// v0.01 2016-02-22 Setup
//
//
// USAGE:
//
// In ONE source file, put:
//
// #define JAR_XM_IMPLEMENTATION
// #include "jar_xm.h"
//
// Other source files should just include jar_xm.h
//
// SAMPLE CODE:
//
// jar_xm_context_t *musicptr;
// float musicBuffer[48000 / 60];
// int intro_load(void)
// {
// jar_xm_create_context_from_file(&musicptr, 48000, "Song.XM");
// return 1;
// }
// int intro_unload(void)
// {
// jar_xm_free_context(musicptr);
// return 1;
// }
// int intro_tick(long counter)
// {
// jar_xm_generate_samples(musicptr, musicBuffer, (48000 / 60) / 2);
// if(IsKeyDown(KEY_ENTER))
// return 1;
// return 0;
// }
//
//
// LISCENSE - FOR LIBXM:
//
// Author: Romain "Artefact2" Dalmaso <artefact2@gmail.com>
// Contributor: Dan Spencer <dan@atomicpotato.net>
// Repackaged into jar_xm.h By: Joshua Adam Reisenauer <kd7tck@gmail.com>
// This program is free software. It comes without any warranty, to the
// extent permitted by applicable law. You can redistribute it and/or
// modify it under the terms of the Do What The Fuck You Want To Public
// License, Version 2, as published by Sam Hocevar. See
// http://sam.zoy.org/wtfpl/COPYING for more details.
#ifndef INCLUDE_JAR_XM_H
#define INCLUDE_JAR_XM_H
#define JAR_XM_DEBUG 0
#define JAR_XM_LINEAR_INTERPOLATION 1 // speed increase with decrease in quality
#define JAR_XM_DEFENSIVE 1
#define JAR_XM_RAMPING 1
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <limits.h>
#include <string.h>
#ifndef true
#include <stdbool.h>
#endif
//-------------------------------------------------------------------------------
#ifdef __cplusplus
extern
"C"
{
#endif
struct
jar_xm_context_s
;
typedef
struct
jar_xm_context_s
jar_xm_context_t
;
/** Create a XM context.
*
* @param moddata the contents of the module
* @param rate play rate in Hz, recommended value of 48000
*
* @returns 0 on success
* @returns 1 if module data is not sane
* @returns 2 if memory allocation failed
* @returns 3 unable to open input file
* @returns 4 fseek() failed
* @returns 5 fread() failed
* @returns 6 unkown error
*
* @deprecated This function is unsafe!
* @see jar_xm_create_context_safe()
*/
int
jar_xm_create_context_from_file
(
jar_xm_context_t
**
ctx
,
uint32_t
rate
,
const
char
*
filename
);
/** Create a XM context.
*
* @param moddata the contents of the module
* @param rate play rate in Hz, recommended value of 48000
*
* @returns 0 on success
* @returns 1 if module data is not sane
* @returns 2 if memory allocation failed
*
* @deprecated This function is unsafe!
* @see jar_xm_create_context_safe()
*/
int
jar_xm_create_context
(
jar_xm_context_t
**
,
const
char
*
moddata
,
uint32_t
rate
);
/** Create a XM context.
*
* @param moddata the contents of the module
* @param moddata_length the length of the contents of the module, in bytes
* @param rate play rate in Hz, recommended value of 48000
*
* @returns 0 on success
* @returns 1 if module data is not sane
* @returns 2 if memory allocation failed
*/
int
jar_xm_create_context_safe
(
jar_xm_context_t
**
,
const
char
*
moddata
,
size_t
moddata_length
,
uint32_t
rate
);
/** Free a XM context created by jar_xm_create_context(). */
void
jar_xm_free_context
(
jar_xm_context_t
*
);
/** Play the module and put the sound samples in an output buffer.
*
* @param output buffer of 2*numsamples elements (A left and right value for each sample)
* @param numsamples number of samples to generate
*/
void
jar_xm_generate_samples
(
jar_xm_context_t
*
,
float
*
output
,
size_t
numsamples
);
/** Play the module, resample from 32 bit to 16 bit, and put the sound samples in an output buffer.
*
* @param output buffer of 2*numsamples elements (A left and right value for each sample)
* @param numsamples number of samples to generate
*/
void
jar_xm_generate_samples_16bit
(
jar_xm_context_t
*
ctx
,
short
*
output
,
size_t
numsamples
)
{
float
*
musicBuffer
=
malloc
((
2
*
numsamples
)
*
sizeof
(
float
));
jar_xm_generate_samples
(
ctx
,
musicBuffer
,
numsamples
);
if
(
output
){
int
x
;
for
(
x
=
0
;
x
<
2
*
numsamples
;
x
++
)
output
[
x
]
=
musicBuffer
[
x
]
*
SHRT_MAX
;
}
free
(
musicBuffer
);
}
/** Play the module, resample from 32 bit to 8 bit, and put the sound samples in an output buffer.
*
* @param output buffer of 2*numsamples elements (A left and right value for each sample)
* @param numsamples number of samples to generate
*/
void
jar_xm_generate_samples_8bit
(
jar_xm_context_t
*
ctx
,
char
*
output
,
size_t
numsamples
)
{
float
*
musicBuffer
=
malloc
((
2
*
numsamples
)
*
sizeof
(
float
));
jar_xm_generate_samples
(
ctx
,
musicBuffer
,
numsamples
);
if
(
output
){
int
x
;
for
(
x
=
0
;
x
<
2
*
numsamples
;
x
++
)
output
[
x
]
=
musicBuffer
[
x
]
*
CHAR_MAX
;
}
free
(
musicBuffer
);
}
/** Set the maximum number of times a module can loop. After the
* specified number of loops, calls to jar_xm_generate_samples will only
* generate silence. You can control the current number of loops with
* jar_xm_get_loop_count().
*
* @param loopcnt maximum number of loops. Use 0 to loop
* indefinitely. */
void
jar_xm_set_max_loop_count
(
jar_xm_context_t
*
,
uint8_t
loopcnt
);
/** Get the loop count of the currently playing module. This value is
* 0 when the module is still playing, 1 when the module has looped
* once, etc. */
uint8_t
jar_xm_get_loop_count
(
jar_xm_context_t
*
);
/** Mute or unmute a channel.
*
* @note Channel numbers go from 1 to jar_xm_get_number_of_channels(...).
*
* @return whether the channel was muted.
*/
bool
jar_xm_mute_channel
(
jar_xm_context_t
*
,
uint16_t
,
bool
);
/** Mute or unmute an instrument.
*
* @note Instrument numbers go from 1 to
* jar_xm_get_number_of_instruments(...).
*
* @return whether the instrument was muted.
*/
bool
jar_xm_mute_instrument
(
jar_xm_context_t
*
,
uint16_t
,
bool
);
/** Get the module name as a NUL-terminated string. */
const
char
*
jar_xm_get_module_name
(
jar_xm_context_t
*
);
/** Get the tracker name as a NUL-terminated string. */
const
char
*
jar_xm_get_tracker_name
(
jar_xm_context_t
*
);
/** Get the number of channels. */
uint16_t
jar_xm_get_number_of_channels
(
jar_xm_context_t
*
);
/** Get the module length (in patterns). */
uint16_t
jar_xm_get_module_length
(
jar_xm_context_t
*
);
/** Get the number of patterns. */
uint16_t
jar_xm_get_number_of_patterns
(
jar_xm_context_t
*
);
/** Get the number of rows of a pattern.
*
* @note Pattern numbers go from 0 to
* jar_xm_get_number_of_patterns(...)-1.
*/
uint16_t
jar_xm_get_number_of_rows
(
jar_xm_context_t
*
,
uint16_t
);
/** Get the number of instruments. */
uint16_t
jar_xm_get_number_of_instruments
(
jar_xm_context_t
*
);
/** Get the number of samples of an instrument.
*
* @note Instrument numbers go from 1 to
* jar_xm_get_number_of_instruments(...).
*/
uint16_t
jar_xm_get_number_of_samples
(
jar_xm_context_t
*
,
uint16_t
);
/** Get the current module speed.
*
* @param bpm will receive the current BPM
* @param tempo will receive the current tempo (ticks per line)
*/
void
jar_xm_get_playing_speed
(
jar_xm_context_t
*
,
uint16_t
*
bpm
,
uint16_t
*
tempo
);
/** Get the current position in the module being played.
*
* @param pattern_index if not NULL, will receive the current pattern
* index in the POT (pattern order table)
*
* @param pattern if not NULL, will receive the current pattern number
*
* @param row if not NULL, will receive the current row
*
* @param samples if not NULL, will receive the total number of
* generated samples (divide by sample rate to get seconds of
* generated audio)
*/
void
jar_xm_get_position
(
jar_xm_context_t
*
,
uint8_t
*
pattern_index
,
uint8_t
*
pattern
,
uint8_t
*
row
,
uint64_t
*
samples
);
/** Get the latest time (in number of generated samples) when a
* particular instrument was triggered in any channel.
*
* @note Instrument numbers go from 1 to
* jar_xm_get_number_of_instruments(...).
*/
uint64_t
jar_xm_get_latest_trigger_of_instrument
(
jar_xm_context_t
*
,
uint16_t
);
/** Get the latest time (in number of generated samples) when a
* particular sample was triggered in any channel.
*
* @note Instrument numbers go from 1 to
* jar_xm_get_number_of_instruments(...).
*
* @note Sample numbers go from 0 to
* jar_xm_get_nubmer_of_samples(...,instr)-1.
*/
uint64_t
jar_xm_get_latest_trigger_of_sample
(
jar_xm_context_t
*
,
uint16_t
instr
,
uint16_t
sample
);
/** Get the latest time (in number of generated samples) when any
* instrument was triggered in a given channel.
*
* @note Channel numbers go from 1 to jar_xm_get_number_of_channels(...).
*/
uint64_t
jar_xm_get_latest_trigger_of_channel
(
jar_xm_context_t
*
,
uint16_t
);
/** Get the number of remaining samples. Divide by 2 to get the number of individual LR data samples.
*
* @note This is the remaining number of samples before the loop starts module again, or halts if on last pass.
* @note This function is very slow and should only be run once, if at all.
*/
uint64_t
jar_xm_get_remaining_samples
(
jar_xm_context_t
*
);
#ifdef __cplusplus
}
#endif
//-------------------------------------------------------------------------------
//Function Definitions-----------------------------------------------------------
#ifdef JAR_XM_IMPLEMENTATION
#include <math.h>
#include <string.h>
#if JAR_XM_DEBUG
#include <stdio.h>
#define DEBUG(fmt, ...) do { \
fprintf(stderr, "%s(): " fmt "\n", __func__, __VA_ARGS__); \
fflush(stderr); \
} while(0)
#else
#define DEBUG(...)
#endif
#if jar_xm_BIG_ENDIAN
#error "Big endian platforms are not yet supported, sorry"
/* Make sure the compiler stops, even if #error is ignored */
extern
int
__fail
[
-
1
];
#endif
/* ----- XM constants ----- */
#define SAMPLE_NAME_LENGTH 22
#define INSTRUMENT_NAME_LENGTH 22
#define MODULE_NAME_LENGTH 20
#define TRACKER_NAME_LENGTH 20
#define PATTERN_ORDER_TABLE_LENGTH 256
#define NUM_NOTES 96
#define NUM_ENVELOPE_POINTS 12
#define MAX_NUM_ROWS 256
#if JAR_XM_RAMPING
#define jar_xm_SAMPLE_RAMPING_POINTS 0x20
#endif
/* ----- Data types ----- */
enum
jar_xm_waveform_type_e
{
jar_xm_SINE_WAVEFORM
=
0
,
jar_xm_RAMP_DOWN_WAVEFORM
=
1
,
jar_xm_SQUARE_WAVEFORM
=
2
,
jar_xm_RANDOM_WAVEFORM
=
3
,
jar_xm_RAMP_UP_WAVEFORM
=
4
,
};
typedef
enum
jar_xm_waveform_type_e
jar_xm_waveform_type_t
;
enum
jar_xm_loop_type_e
{
jar_xm_NO_LOOP
,
jar_xm_FORWARD_LOOP
,
jar_xm_PING_PONG_LOOP
,
};
typedef
enum
jar_xm_loop_type_e
jar_xm_loop_type_t
;
enum
jar_xm_frequency_type_e
{
jar_xm_LINEAR_FREQUENCIES
,
jar_xm_AMIGA_FREQUENCIES
,
};
typedef
enum
jar_xm_frequency_type_e
jar_xm_frequency_type_t
;
struct
jar_xm_envelope_point_s
{
uint16_t
frame
;
uint16_t
value
;
};
typedef
struct
jar_xm_envelope_point_s
jar_xm_envelope_point_t
;
struct
jar_xm_envelope_s
{
jar_xm_envelope_point_t
points
[
NUM_ENVELOPE_POINTS
];
uint8_t
num_points
;
uint8_t
sustain_point
;
uint8_t
loop_start_point
;
uint8_t
loop_end_point
;
bool
enabled
;
bool
sustain_enabled
;
bool
loop_enabled
;
};
typedef
struct
jar_xm_envelope_s
jar_xm_envelope_t
;
struct
jar_xm_sample_s
{
char
name
[
SAMPLE_NAME_LENGTH
+
1
];
int8_t
bits
;
/* Either 8 or 16 */
uint32_t
length
;
uint32_t
loop_start
;
uint32_t
loop_length
;
uint32_t
loop_end
;
float
volume
;
int8_t
finetune
;
jar_xm_loop_type_t
loop_type
;
float
panning
;
int8_t
relative_note
;
uint64_t
latest_trigger
;
float
*
data
;
};
typedef
struct
jar_xm_sample_s
jar_xm_sample_t
;
struct
jar_xm_instrument_s
{
char
name
[
INSTRUMENT_NAME_LENGTH
+
1
];
uint16_t
num_samples
;
uint8_t
sample_of_notes
[
NUM_NOTES
];
jar_xm_envelope_t
volume_envelope
;
jar_xm_envelope_t
panning_envelope
;
jar_xm_waveform_type_t
vibrato_type
;
uint8_t
vibrato_sweep
;
uint8_t
vibrato_depth
;
uint8_t
vibrato_rate
;
uint16_t
volume_fadeout
;
uint64_t
latest_trigger
;
bool
muted
;
jar_xm_sample_t
*
samples
;
};
typedef
struct
jar_xm_instrument_s
jar_xm_instrument_t
;
struct
jar_xm_pattern_slot_s
{
uint8_t
note
;
/* 1-96, 97 = Key Off note */
uint8_t
instrument
;
/* 1-128 */
uint8_t
volume_column
;
uint8_t
effect_type
;
uint8_t
effect_param
;
};
typedef
struct
jar_xm_pattern_slot_s
jar_xm_pattern_slot_t
;
struct
jar_xm_pattern_s
{
uint16_t
num_rows
;
jar_xm_pattern_slot_t
*
slots
;
/* Array of size num_rows * num_channels */
};
typedef
struct
jar_xm_pattern_s
jar_xm_pattern_t
;
struct
jar_xm_module_s
{
char
name
[
MODULE_NAME_LENGTH
+
1
];
char
trackername
[
TRACKER_NAME_LENGTH
+
1
];
uint16_t
length
;
uint16_t
restart_position
;
uint16_t
num_channels
;
uint16_t
num_patterns
;
uint16_t
num_instruments
;
jar_xm_frequency_type_t
frequency_type
;
uint8_t
pattern_table
[
PATTERN_ORDER_TABLE_LENGTH
];
jar_xm_pattern_t
*
patterns
;
jar_xm_instrument_t
*
instruments
;
/* Instrument 1 has index 0,
* instrument 2 has index 1, etc. */
};
typedef
struct
jar_xm_module_s
jar_xm_module_t
;
struct
jar_xm_channel_context_s
{
float
note
;
float
orig_note
;
/* The original note before effect modifications, as read in the pattern. */
jar_xm_instrument_t
*
instrument
;
/* Could be NULL */
jar_xm_sample_t
*
sample
;
/* Could be NULL */
jar_xm_pattern_slot_t
*
current
;
float
sample_position
;
float
period
;
float
frequency
;
float
step
;
bool
ping
;
/* For ping-pong samples: true is -->, false is <-- */
float
volume
;
/* Ideally between 0 (muted) and 1 (loudest) */
float
panning
;
/* Between 0 (left) and 1 (right); 0.5 is centered */
uint16_t
autovibrato_ticks
;
bool
sustained
;
float
fadeout_volume
;
float
volume_envelope_volume
;
float
panning_envelope_panning
;
uint16_t
volume_envelope_frame_count
;
uint16_t
panning_envelope_frame_count
;
float
autovibrato_note_offset
;
bool
arp_in_progress
;
uint8_t
arp_note_offset
;
uint8_t
volume_slide_param
;
uint8_t
fine_volume_slide_param
;
uint8_t
global_volume_slide_param
;
uint8_t
panning_slide_param
;
uint8_t
portamento_up_param
;
uint8_t
portamento_down_param
;
uint8_t
fine_portamento_up_param
;
uint8_t
fine_portamento_down_param
;
uint8_t
extra_fine_portamento_up_param
;
uint8_t
extra_fine_portamento_down_param
;
uint8_t
tone_portamento_param
;
float
tone_portamento_target_period
;
uint8_t
multi_retrig_param
;
uint8_t
note_delay_param
;
uint8_t
pattern_loop_origin
;
/* Where to restart a E6y loop */
uint8_t
pattern_loop_count
;
/* How many loop passes have been done */
bool
vibrato_in_progress
;
jar_xm_waveform_type_t
vibrato_waveform
;
bool
vibrato_waveform_retrigger
;
/* True if a new note retriggers the waveform */
uint8_t
vibrato_param
;
uint16_t
vibrato_ticks
;
/* Position in the waveform */
float
vibrato_note_offset
;
jar_xm_waveform_type_t
tremolo_waveform
;
bool
tremolo_waveform_retrigger
;
uint8_t
tremolo_param
;
uint8_t
tremolo_ticks
;
float
tremolo_volume
;
uint8_t
tremor_param
;
bool
tremor_on
;
uint64_t
latest_trigger
;
bool
muted
;
#if JAR_XM_RAMPING
/* These values are updated at the end of each tick, to save
* a couple of float operations on every generated sample. */
float
target_panning
;
float
target_volume
;
unsigned
long
frame_count
;
float
end_of_previous_sample
[
jar_xm_SAMPLE_RAMPING_POINTS
];
#endif
float
actual_panning
;
float
actual_volume
;
};
typedef
struct
jar_xm_channel_context_s
jar_xm_channel_context_t
;
struct
jar_xm_context_s
{
void
*
allocated_memory
;
jar_xm_module_t
module
;
uint32_t
rate
;
uint16_t
tempo
;
uint16_t
bpm
;
float
global_volume
;
float
amplification
;
#if JAR_XM_RAMPING
/* How much is a channel final volume allowed to change per
* sample; this is used to avoid abrubt volume changes which
* manifest as "clicks" in the generated sound. */
float
volume_ramp
;
float
panning_ramp
;
/* Same for panning. */
#endif
uint8_t
current_table_index
;
uint8_t
current_row
;
uint16_t
current_tick
;
/* Can go below 255, with high tempo and a pattern delay */
float
remaining_samples_in_tick
;
uint64_t
generated_samples
;
bool
position_jump
;
bool
pattern_break
;
uint8_t
jump_dest
;
uint8_t
jump_row
;
/* Extra ticks to be played before going to the next row -
* Used for EEy effect */
uint16_t
extra_ticks
;
uint8_t
*
row_loop_count
;
/* Array of size MAX_NUM_ROWS * module_length */
uint8_t
loop_count
;
uint8_t
max_loop_count
;
jar_xm_channel_context_t
*
channels
;
};
/* ----- Internal API ----- */
#if JAR_XM_DEFENSIVE
/** Check the module data for errors/inconsistencies.
*
* @returns 0 if everything looks OK. Module should be safe to load.
*/
int
jar_xm_check_sanity_preload
(
const
char
*
,
size_t
);
/** Check a loaded module for errors/inconsistencies.
*
* @returns 0 if everything looks OK.
*/
int
jar_xm_check_sanity_postload
(
jar_xm_context_t
*
);
#endif
/** Get the number of bytes needed to store the module data in a
* dynamically allocated blank context.
*
* Things that are dynamically allocated:
* - sample data
* - sample structures in instruments
* - pattern data
* - row loop count arrays
* - pattern structures in module
* - instrument structures in module
* - channel contexts
* - context structure itself
* @returns 0 if everything looks OK.
*/
size_t
jar_xm_get_memory_needed_for_context
(
const
char
*
,
size_t
);
/** Populate the context from module data.
*
* @returns pointer to the memory pool
*/
char
*
jar_xm_load_module
(
jar_xm_context_t
*
,
const
char
*
,
size_t
,
char
*
);
int
jar_xm_create_context
(
jar_xm_context_t
**
ctxp
,
const
char
*
moddata
,
uint32_t
rate
)
{
return
jar_xm_create_context_safe
(
ctxp
,
moddata
,
SIZE_MAX
,
rate
);
}
int
jar_xm_create_context_safe
(
jar_xm_context_t
**
ctxp
,
const
char
*
moddata
,
size_t
moddata_length
,
uint32_t
rate
)
{
#if JAR_XM_DEFENSIVE
int
ret
;
#endif
size_t
bytes_needed
;
char
*
mempool
;
jar_xm_context_t
*
ctx
;
#if JAR_XM_DEFENSIVE
if
((
ret
=
jar_xm_check_sanity_preload
(
moddata
,
moddata_length
)))
{
DEBUG
(
"jar_xm_check_sanity_preload() returned %i, module is not safe to load"
,
ret
);
return
1
;
}
#endif
bytes_needed
=
jar_xm_get_memory_needed_for_context
(
moddata
,
moddata_length
);
mempool
=
malloc
(
bytes_needed
);
if
(
mempool
==
NULL
&&
bytes_needed
>
0
)
{
/* malloc() failed, trouble ahead */
DEBUG
(
"call to malloc() failed, returned %p"
,
(
void
*
)
mempool
);
return
2
;
}
/* Initialize most of the fields to 0, 0.f, NULL or false depending on type */
memset
(
mempool
,
0
,
bytes_needed
);
ctx
=
(
*
ctxp
=
(
jar_xm_context_t
*
)
mempool
);
ctx
->
allocated_memory
=
mempool
;
/* Keep original pointer for free() */
mempool
+=
sizeof
(
jar_xm_context_t
);
ctx
->
rate
=
rate
;
mempool
=
jar_xm_load_module
(
ctx
,
moddata
,
moddata_length
,
mempool
);
ctx
->
channels
=
(
jar_xm_channel_context_t
*
)
mempool
;
mempool
+=
ctx
->
module
.
num_channels
*
sizeof
(
jar_xm_channel_context_t
);
ctx
->
global_volume
=
1
.
f
;
ctx
->
amplification
=
.
25
f
;
/* XXX: some bad modules may still clip. Find out something better. */
#if JAR_XM_RAMPING
ctx
->
volume_ramp
=
(
1
.
f
/
128
.
f
);
ctx
->
panning_ramp
=
(
1
.
f
/
128
.
f
);
#endif
for
(
uint8_t
i
=
0
;
i
<
ctx
->
module
.
num_channels
;
++
i
)
{
jar_xm_channel_context_t
*
ch
=
ctx
->
channels
+
i
;
ch
->
ping
=
true
;
ch
->
vibrato_waveform
=
jar_xm_SINE_WAVEFORM
;
ch
->
vibrato_waveform_retrigger
=
true
;
ch
->
tremolo_waveform
=
jar_xm_SINE_WAVEFORM
;
ch
->
tremolo_waveform_retrigger
=
true
;
ch
->
volume
=
ch
->
volume_envelope_volume
=
ch
->
fadeout_volume
=
1
.
0
f
;
ch
->
panning
=
ch
->
panning_envelope_panning
=
.
5
f
;
ch
->
actual_volume
=
.
0
f
;
ch
->
actual_panning
=
.
5
f
;
}
ctx
->
row_loop_count
=
(
uint8_t
*
)
mempool
;
mempool
+=
MAX_NUM_ROWS
*
sizeof
(
uint8_t
);
#if JAR_XM_DEFENSIVE
if
((
ret
=
jar_xm_check_sanity_postload
(
ctx
)))
{
DEBUG
(
"jar_xm_check_sanity_postload() returned %i, module is not safe to play"
,
ret
);
jar_xm_free_context
(
ctx
);
return
1
;
}
#endif
return
0
;
}
void
jar_xm_free_context
(
jar_xm_context_t
*
context
)
{
free
(
context
->
allocated_memory
);
}
void
jar_xm_set_max_loop_count
(
jar_xm_context_t
*
context
,
uint8_t
loopcnt
)
{
context
->
max_loop_count
=
loopcnt
;
}
uint8_t
jar_xm_get_loop_count
(
jar_xm_context_t
*
context
)
{
return
context
->
loop_count
;
}
bool
jar_xm_mute_channel
(
jar_xm_context_t
*
ctx
,
uint16_t
channel
,
bool
mute
)
{
bool
old
=
ctx
->
channels
[
channel
-
1
].
muted
;
ctx
->
channels
[
channel
-
1
].
muted
=
mute
;
return
old
;
}
bool
jar_xm_mute_instrument
(
jar_xm_context_t
*
ctx
,
uint16_t
instr
,
bool
mute
)
{
bool
old
=
ctx
->
module
.
instruments
[
instr
-
1
].
muted
;
ctx
->
module
.
instruments
[
instr
-
1
].
muted
=
mute
;
return
old
;
}
const
char
*
jar_xm_get_module_name
(
jar_xm_context_t
*
ctx
)
{
return
ctx
->
module
.
name
;
}
const
char
*
jar_xm_get_tracker_name
(
jar_xm_context_t
*
ctx
)
{
return
ctx
->
module
.
trackername
;
}
uint16_t
jar_xm_get_number_of_channels
(
jar_xm_context_t
*
ctx
)
{
return
ctx
->
module
.
num_channels
;
}
uint16_t
jar_xm_get_module_length
(
jar_xm_context_t
*
ctx
)
{
return
ctx
->
module
.
length
;
}
uint16_t
jar_xm_get_number_of_patterns
(
jar_xm_context_t
*
ctx
)
{
return
ctx
->
module
.
num_patterns
;
}
uint16_t
jar_xm_get_number_of_rows
(
jar_xm_context_t
*
ctx
,
uint16_t
pattern
)
{
return
ctx
->
module
.
patterns
[
pattern
].
num_rows
;
}
uint16_t
jar_xm_get_number_of_instruments
(
jar_xm_context_t
*
ctx
)
{
return
ctx
->
module
.
num_instruments
;
}
uint16_t
jar_xm_get_number_of_samples
(
jar_xm_context_t
*
ctx
,
uint16_t
instrument
)
{
return
ctx
->
module
.
instruments
[
instrument
-
1
].
num_samples
;
}
void
jar_xm_get_playing_speed
(
jar_xm_context_t
*
ctx
,
uint16_t
*
bpm
,
uint16_t
*
tempo
)
{
if
(
bpm
)
*
bpm
=
ctx
->
bpm
;
if
(
tempo
)
*
tempo
=
ctx
->
tempo
;
}
void
jar_xm_get_position
(
jar_xm_context_t
*
ctx
,
uint8_t
*
pattern_index
,
uint8_t
*
pattern
,
uint8_t
*
row
,
uint64_t
*
samples
)
{
if
(
pattern_index
)
*
pattern_index
=
ctx
->
current_table_index
;
if
(
pattern
)
*
pattern
=
ctx
->
module
.
pattern_table
[
ctx
->
current_table_index
];
if
(
row
)
*
row
=
ctx
->
current_row
;
if
(
samples
)
*
samples
=
ctx
->
generated_samples
;
}
uint64_t
jar_xm_get_latest_trigger_of_instrument
(
jar_xm_context_t
*
ctx
,
uint16_t
instr
)
{
return
ctx
->
module
.
instruments
[
instr
-
1
].
latest_trigger
;
}
uint64_t
jar_xm_get_latest_trigger_of_sample
(
jar_xm_context_t
*
ctx
,
uint16_t
instr
,
uint16_t
sample
)
{
return
ctx
->
module
.
instruments
[
instr
-
1
].
samples
[
sample
].
latest_trigger
;
}
uint64_t
jar_xm_get_latest_trigger_of_channel
(
jar_xm_context_t
*
ctx
,
uint16_t
chn
)
{
return
ctx
->
channels
[
chn
-
1
].
latest_trigger
;
}
/* .xm files are little-endian. (XXX: Are they really?) */
/* Bounded reader macros.
* If we attempt to read the buffer out-of-bounds, pretend that the buffer is
* infinitely padded with zeroes.
*/
#define READ_U8(offset) (((offset) < moddata_length) ? (*(uint8_t*)(moddata + (offset))) : 0)
#define READ_U16(offset) ((uint16_t)READ_U8(offset) | ((uint16_t)READ_U8((offset) + 1) << 8))
#define READ_U32(offset) ((uint32_t)READ_U16(offset) | ((uint32_t)READ_U16((offset) + 2) << 16))
#define READ_MEMCPY(ptr, offset, length) memcpy_pad(ptr, length, moddata, moddata_length, offset)
static
inline
void
memcpy_pad
(
void
*
dst
,
size_t
dst_len
,
const
void
*
src
,
size_t
src_len
,
size_t
offset
)
{
uint8_t
*
dst_c
=
dst
;
const
uint8_t
*
src_c
=
src
;
/* how many bytes can be copied without overrunning `src` */
size_t
copy_bytes
=
(
src_len
>=
offset
)
?
(
src_len
-
offset
)
:
0
;
copy_bytes
=
copy_bytes
>
dst_len
?
dst_len
:
copy_bytes
;
memcpy
(
dst_c
,
src_c
+
offset
,
copy_bytes
);
/* padded bytes */
memset
(
dst_c
+
copy_bytes
,
0
,
dst_len
-
copy_bytes
);
}
#if JAR_XM_DEFENSIVE
int
jar_xm_check_sanity_preload
(
const
char
*
module
,
size_t
module_length
)
{
if
(
module_length
<
60
)
{
return
4
;
}
if
(
memcmp
(
"Extended Module: "
,
module
,
17
)
!=
0
)
{
return
1
;
}
if
(
module
[
37
]
!=
0x1A
)
{
return
2
;
}
if
(
module
[
59
]
!=
0x01
||
module
[
58
]
!=
0x04
)
{
/* Not XM 1.04 */
return
3
;
}
return
0
;
}
int
jar_xm_check_sanity_postload
(
jar_xm_context_t
*
ctx
)
{
/* @todo: plenty of stuff to do here… */
/* Check the POT */
for
(
uint8_t
i
=
0
;
i
<
ctx
->
module
.
length
;
++
i
)
{
if
(
ctx
->
module
.
pattern_table
[
i
]
>=
ctx
->
module
.
num_patterns
)
{
if
(
i
+
1
==
ctx
->
module
.
length
&&
ctx
->
module
.
length
>
1
)
{
/* Cheap fix */
--
ctx
->
module
.
length
;
DEBUG
(
"trimming invalid POT at pos %X"
,
i
);
}
else
{
DEBUG
(
"module has invalid POT, pos %X references nonexistent pattern %X"
,
i
,
ctx
->
module
.
pattern_table
[
i
]);
return
1
;
}
}
}
return
0
;
}
#endif
size_t
jar_xm_get_memory_needed_for_context
(
const
char
*
moddata
,
size_t
moddata_length
)
{
size_t
memory_needed
=
0
;
size_t
offset
=
60
;
/* Skip the first header */
uint16_t
num_channels
;
uint16_t
num_patterns
;
uint16_t
num_instruments
;
/* Read the module header */
num_channels
=
READ_U16
(
offset
+
8
);
num_channels
=
READ_U16
(
offset
+
8
);
num_patterns
=
READ_U16
(
offset
+
10
);
memory_needed
+=
num_patterns
*
sizeof
(
jar_xm_pattern_t
);
num_instruments
=
READ_U16
(
offset
+
12
);
memory_needed
+=
num_instruments
*
sizeof
(
jar_xm_instrument_t
);
memory_needed
+=
MAX_NUM_ROWS
*
READ_U16
(
offset
+
4
)
*
sizeof
(
uint8_t
);
/* Module length */
/* Header size */
offset
+=
READ_U32
(
offset
);
/* Read pattern headers */
for
(
uint16_t
i
=
0
;
i
<
num_patterns
;
++
i
)
{
uint16_t
num_rows
;
num_rows
=
READ_U16
(
offset
+
5
);
memory_needed
+=
num_rows
*
num_channels
*
sizeof
(
jar_xm_pattern_slot_t
);
/* Pattern header length + packed pattern data size */
offset
+=
READ_U32
(
offset
)
+
READ_U16
(
offset
+
7
);
}
/* Read instrument headers */
for
(
uint16_t
i
=
0
;
i
<
num_instruments
;
++
i
)
{
uint16_t
num_samples
;
uint32_t
sample_header_size
=
0
;
uint32_t
sample_size_aggregate
=
0
;
num_samples
=
READ_U16
(
offset
+
27
);
memory_needed
+=
num_samples
*
sizeof
(
jar_xm_sample_t
);
if
(
num_samples
>
0
)
{
sample_header_size
=
READ_U32
(
offset
+
29
);
}
/* Instrument header size */
offset
+=
READ_U32
(
offset
);
for
(
uint16_t
j
=
0
;
j
<
num_samples
;
++
j
)
{
uint32_t
sample_size
;
uint8_t
flags
;
sample_size
=
READ_U32
(
offset
);
flags
=
READ_U8
(
offset
+
14
);
sample_size_aggregate
+=
sample_size
;
if
(
flags
&
(
1
<<
4
))
{
/* 16 bit sample */
memory_needed
+=
sample_size
*
(
sizeof
(
float
)
>>
1
);
}
else
{
/* 8 bit sample */
memory_needed
+=
sample_size
*
sizeof
(
float
);
}
offset
+=
sample_header_size
;
}
offset
+=
sample_size_aggregate
;
}
memory_needed
+=
num_channels
*
sizeof
(
jar_xm_channel_context_t
);
memory_needed
+=
sizeof
(
jar_xm_context_t
);
return
memory_needed
;
}
char
*
jar_xm_load_module
(
jar_xm_context_t
*
ctx
,
const
char
*
moddata
,
size_t
moddata_length
,
char
*
mempool
)
{
size_t
offset
=
0
;
jar_xm_module_t
*
mod
=
&
(
ctx
->
module
);
/* Read XM header */
READ_MEMCPY
(
mod
->
name
,
offset
+
17
,
MODULE_NAME_LENGTH
);
READ_MEMCPY
(
mod
->
trackername
,
offset
+
38
,
TRACKER_NAME_LENGTH
);
offset
+=
60
;
/* Read module header */
uint32_t
header_size
=
READ_U32
(
offset
);
mod
->
length
=
READ_U16
(
offset
+
4
);
mod
->
restart_position
=
READ_U16
(
offset
+
6
);
mod
->
num_channels
=
READ_U16
(
offset
+
8
);
mod
->
num_patterns
=
READ_U16
(
offset
+
10
);
mod
->
num_instruments
=
READ_U16
(
offset
+
12
);
mod
->
patterns
=
(
jar_xm_pattern_t
*
)
mempool
;
mempool
+=
mod
->
num_patterns
*
sizeof
(
jar_xm_pattern_t
);
mod
->
instruments
=
(
jar_xm_instrument_t
*
)
mempool
;
mempool
+=
mod
->
num_instruments
*
sizeof
(
jar_xm_instrument_t
);
uint16_t
flags
=
READ_U32
(
offset
+
14
);
mod
->
frequency_type
=
(
flags
&
(
1
<<
0
))
?
jar_xm_LINEAR_FREQUENCIES
:
jar_xm_AMIGA_FREQUENCIES
;
ctx
->
tempo
=
READ_U16
(
offset
+
16
);
ctx
->
bpm
=
READ_U16
(
offset
+
18
);
READ_MEMCPY
(
mod
->
pattern_table
,
offset
+
20
,
PATTERN_ORDER_TABLE_LENGTH
);
offset
+=
header_size
;
/* Read patterns */
for
(
uint16_t
i
=
0
;
i
<
mod
->
num_patterns
;
++
i
)
{
uint16_t
packed_patterndata_size
=
READ_U16
(
offset
+
7
);
jar_xm_pattern_t
*
pat
=
mod
->
patterns
+
i
;
pat
->
num_rows
=
READ_U16
(
offset
+
5
);
pat
->
slots
=
(
jar_xm_pattern_slot_t
*
)
mempool
;
mempool
+=
mod
->
num_channels
*
pat
->
num_rows
*
sizeof
(
jar_xm_pattern_slot_t
);
/* Pattern header length */
offset
+=
READ_U32
(
offset
);
if
(
packed_patterndata_size
==
0
)
{
/* No pattern data is present */
memset
(
pat
->
slots
,
0
,
sizeof
(
jar_xm_pattern_slot_t
)
*
pat
->
num_rows
*
mod
->
num_channels
);
}
else
{
/* This isn't your typical for loop */
for
(
uint16_t
j
=
0
,
k
=
0
;
j
<
packed_patterndata_size
;
++
k
)
{
uint8_t
note
=
READ_U8
(
offset
+
j
);
jar_xm_pattern_slot_t
*
slot
=
pat
->
slots
+
k
;
if
(
note
&
(
1
<<
7
))
{
/* MSB is set, this is a compressed packet */
++
j
;
if
(
note
&
(
1
<<
0
))
{
/* Note follows */
slot
->
note
=
READ_U8
(
offset
+
j
);
++
j
;
}
else
{
slot
->
note
=
0
;
}
if
(
note
&
(
1
<<
1
))
{
/* Instrument follows */
slot
->
instrument
=
READ_U8
(
offset
+
j
);
++
j
;
}
else
{
slot
->
instrument
=
0
;
}
if
(
note
&
(
1
<<
2
))
{
/* Volume column follows */
slot
->
volume_column
=
READ_U8
(
offset
+
j
);
++
j
;
}
else
{
slot
->
volume_column
=
0
;
}
if
(
note
&
(
1
<<
3
))
{
/* Effect follows */
slot
->
effect_type
=
READ_U8
(
offset
+
j
);
++
j
;
}
else
{
slot
->
effect_type
=
0
;
}
if
(
note
&
(
1
<<
4
))
{
/* Effect parameter follows */
slot
->
effect_param
=
READ_U8
(
offset
+
j
);
++
j
;
}
else
{
slot
->
effect_param
=
0
;
}
}
else
{
/* Uncompressed packet */
slot
->
note
=
note
;
slot
->
instrument
=
READ_U8
(
offset
+
j
+
1
);
slot
->
volume_column
=
READ_U8
(
offset
+
j
+
2
);
slot
->
effect_type
=
READ_U8
(
offset
+
j
+
3
);
slot
->
effect_param
=
READ_U8
(
offset
+
j
+
4
);
j
+=
5
;
}
}
}
offset
+=
packed_patterndata_size
;
}
/* Read instruments */
for
(
uint16_t
i
=
0
;
i
<
ctx
->
module
.
num_instruments
;
++
i
)
{
uint32_t
sample_header_size
=
0
;
jar_xm_instrument_t
*
instr
=
mod
->
instruments
+
i
;
READ_MEMCPY
(
instr
->
name
,
offset
+
4
,
INSTRUMENT_NAME_LENGTH
);
instr
->
num_samples
=
READ_U16
(
offset
+
27
);
if
(
instr
->
num_samples
>
0
)
{
/* Read extra header properties */
sample_header_size
=
READ_U32
(
offset
+
29
);
READ_MEMCPY
(
instr
->
sample_of_notes
,
offset
+
33
,
NUM_NOTES
);
instr
->
volume_envelope
.
num_points
=
READ_U8
(
offset
+
225
);
instr
->
panning_envelope
.
num_points
=
READ_U8
(
offset
+
226
);
for
(
uint8_t
j
=
0
;
j
<
instr
->
volume_envelope
.
num_points
;
++
j
)
{
instr
->
volume_envelope
.
points
[
j
].
frame
=
READ_U16
(
offset
+
129
+
4
*
j
);
instr
->
volume_envelope
.
points
[
j
].
value
=
READ_U16
(
offset
+
129
+
4
*
j
+
2
);
}
for
(
uint8_t
j
=
0
;
j
<
instr
->
panning_envelope
.
num_points
;
++
j
)
{
instr
->
panning_envelope
.
points
[
j
].
frame
=
READ_U16
(
offset
+
177
+
4
*
j
);
instr
->
panning_envelope
.
points
[
j
].
value
=
READ_U16
(
offset
+
177
+
4
*
j
+
2
);
}
instr
->
volume_envelope
.
sustain_point
=
READ_U8
(
offset
+
227
);
instr
->
volume_envelope
.
loop_start_point
=
READ_U8
(
offset
+
228
);
instr
->
volume_envelope
.
loop_end_point
=
READ_U8
(
offset
+
229
);
instr
->
panning_envelope
.
sustain_point
=
READ_U8
(
offset
+
230
);
instr
->
panning_envelope
.
loop_start_point
=
READ_U8
(
offset
+
231
);
instr
->
panning_envelope
.
loop_end_point
=
READ_U8
(
offset
+
232
);
uint8_t
flags
=
READ_U8
(
offset
+
233
);
instr
->
volume_envelope
.
enabled
=
flags
&
(
1
<<
0
);
instr
->
volume_envelope
.
sustain_enabled
=
flags
&
(
1
<<
1
);
instr
->
volume_envelope
.
loop_enabled
=
flags
&
(
1
<<
2
);
flags
=
READ_U8
(
offset
+
234
);
instr
->
panning_envelope
.
enabled
=
flags
&
(
1
<<
0
);
instr
->
panning_envelope
.
sustain_enabled
=
flags
&
(
1
<<
1
);
instr
->
panning_envelope
.
loop_enabled
=
flags
&
(
1
<<
2
);
instr
->
vibrato_type
=
READ_U8
(
offset
+
235
);
if
(
instr
->
vibrato_type
==
2
)
{
instr
->
vibrato_type
=
1
;
}
else
if
(
instr
->
vibrato_type
==
1
)
{
instr
->
vibrato_type
=
2
;
}
instr
->
vibrato_sweep
=
READ_U8
(
offset
+
236
);
instr
->
vibrato_depth
=
READ_U8
(
offset
+
237
);
instr
->
vibrato_rate
=
READ_U8
(
offset
+
238
);
instr
->
volume_fadeout
=
READ_U16
(
offset
+
239
);
instr
->
samples
=
(
jar_xm_sample_t
*
)
mempool
;
mempool
+=
instr
->
num_samples
*
sizeof
(
jar_xm_sample_t
);
}
else
{
instr
->
samples
=
NULL
;
}
/* Instrument header size */
offset
+=
READ_U32
(
offset
);
for
(
uint16_t
j
=
0
;
j
<
instr
->
num_samples
;
++
j
)
{
/* Read sample header */
jar_xm_sample_t
*
sample
=
instr
->
samples
+
j
;
sample
->
length
=
READ_U32
(
offset
);
sample
->
loop_start
=
READ_U32
(
offset
+
4
);
sample
->
loop_length
=
READ_U32
(
offset
+
8
);
sample
->
loop_end
=
sample
->
loop_start
+
sample
->
loop_length
;
sample
->
volume
=
(
float
)
READ_U8
(
offset
+
12
)
/
(
float
)
0x40
;
sample
->
finetune
=
(
int8_t
)
READ_U8
(
offset
+
13
);
uint8_t
flags
=
READ_U8
(
offset
+
14
);
if
((
flags
&
3
)
==
0
)
{
sample
->
loop_type
=
jar_xm_NO_LOOP
;
}
else
if
((
flags
&
3
)
==
1
)
{
sample
->
loop_type
=
jar_xm_FORWARD_LOOP
;
}
else
{
sample
->
loop_type
=
jar_xm_PING_PONG_LOOP
;
}
sample
->
bits
=
(
flags
&
(
1
<<
4
))
?
16
:
8
;
sample
->
panning
=
(
float
)
READ_U8
(
offset
+
15
)
/
(
float
)
0xFF
;
sample
->
relative_note
=
(
int8_t
)
READ_U8
(
offset
+
16
);
READ_MEMCPY
(
sample
->
name
,
18
,
SAMPLE_NAME_LENGTH
);
sample
->
data
=
(
float
*
)
mempool
;
if
(
sample
->
bits
==
16
)
{
/* 16 bit sample */
mempool
+=
sample
->
length
*
(
sizeof
(
float
)
>>
1
);
sample
->
loop_start
>>=
1
;
sample
->
loop_length
>>=
1
;
sample
->
loop_end
>>=
1
;
sample
->
length
>>=
1
;
}
else
{
/* 8 bit sample */
mempool
+=
sample
->
length
*
sizeof
(
float
);
}
offset
+=
sample_header_size
;
}
for
(
uint16_t
j
=
0
;
j
<
instr
->
num_samples
;
++
j
)
{
/* Read sample data */
jar_xm_sample_t
*
sample
=
instr
->
samples
+
j
;
uint32_t
length
=
sample
->
length
;
if
(
sample
->
bits
==
16
)
{
int16_t
v
=
0
;
for
(
uint32_t
k
=
0
;
k
<
length
;
++
k
)
{
v
=
v
+
(
int16_t
)
READ_U16
(
offset
+
(
k
<<
1
));
sample
->
data
[
k
]
=
(
float
)
v
/
(
float
)(
1
<<
15
);
}
offset
+=
sample
->
length
<<
1
;
}
else
{
int8_t
v
=
0
;
for
(
uint32_t
k
=
0
;
k
<
length
;
++
k
)
{
v
=
v
+
(
int8_t
)
READ_U8
(
offset
+
k
);
sample
->
data
[
k
]
=
(
float
)
v
/
(
float
)(
1
<<
7
);
}
offset
+=
sample
->
length
;
}
}
}
return
mempool
;
}
//-------------------------------------------------------------------------------
//THE FOLLOWING IS FOR PLAYING
//-------------------------------------------------------------------------------
/* ----- Static functions ----- */
static
float
jar_xm_waveform
(
jar_xm_waveform_type_t
,
uint8_t
);
static
void
jar_xm_autovibrato
(
jar_xm_context_t
*
,
jar_xm_channel_context_t
*
);
static
void
jar_xm_vibrato
(
jar_xm_context_t
*
,
jar_xm_channel_context_t
*
,
uint8_t
,
uint16_t
);
static
void
jar_xm_tremolo
(
jar_xm_context_t
*
,
jar_xm_channel_context_t
*
,
uint8_t
,
uint16_t
);
static
void
jar_xm_arpeggio
(
jar_xm_context_t
*
,
jar_xm_channel_context_t
*
,
uint8_t
,
uint16_t
);
static
void
jar_xm_tone_portamento
(
jar_xm_context_t
*
,
jar_xm_channel_context_t
*
);
static
void
jar_xm_pitch_slide
(
jar_xm_context_t
*
,
jar_xm_channel_context_t
*
,
float
);
static
void
jar_xm_panning_slide
(
jar_xm_channel_context_t
*
,
uint8_t
);
static
void
jar_xm_volume_slide
(
jar_xm_channel_context_t
*
,
uint8_t
);
static
float
jar_xm_envelope_lerp
(
jar_xm_envelope_point_t
*
,
jar_xm_envelope_point_t
*
,
uint16_t
);
static
void
jar_xm_envelope_tick
(
jar_xm_channel_context_t
*
,
jar_xm_envelope_t
*
,
uint16_t
*
,
float
*
);
static
void
jar_xm_envelopes
(
jar_xm_channel_context_t
*
);
static
float
jar_xm_linear_period
(
float
);
static
float
jar_xm_linear_frequency
(
float
);
static
float
jar_xm_amiga_period
(
float
);
static
float
jar_xm_amiga_frequency
(
float
);
static
float
jar_xm_period
(
jar_xm_context_t
*
,
float
);
static
float
jar_xm_frequency
(
jar_xm_context_t
*
,
float
,
float
);
static
void
jar_xm_update_frequency
(
jar_xm_context_t
*
,
jar_xm_channel_context_t
*
);
static
void
jar_xm_handle_note_and_instrument
(
jar_xm_context_t
*
,
jar_xm_channel_context_t
*
,
jar_xm_pattern_slot_t
*
);
static
void
jar_xm_trigger_note
(
jar_xm_context_t
*
,
jar_xm_channel_context_t
*
,
unsigned
int
flags
);
static
void
jar_xm_cut_note
(
jar_xm_channel_context_t
*
);
static
void
jar_xm_key_off
(
jar_xm_channel_context_t
*
);
static
void
jar_xm_post_pattern_change
(
jar_xm_context_t
*
);
static
void
jar_xm_row
(
jar_xm_context_t
*
);
static
void
jar_xm_tick
(
jar_xm_context_t
*
);
static
float
jar_xm_next_of_sample
(
jar_xm_channel_context_t
*
);
static
void
jar_xm_sample
(
jar_xm_context_t
*
,
float
*
,
float
*
);
/* ----- Other oddities ----- */
#define jar_xm_TRIGGER_KEEP_VOLUME (1 << 0)
#define jar_xm_TRIGGER_KEEP_PERIOD (1 << 1)
#define jar_xm_TRIGGER_KEEP_SAMPLE_POSITION (1 << 2)
static
const
uint16_t
amiga_frequencies
[]
=
{
1712
,
1616
,
1525
,
1440
,
/* C-2, C#2, D-2, D#2 */
1357
,
1281
,
1209
,
1141
,
/* E-2, F-2, F#2, G-2 */
1077
,
1017
,
961
,
907
,
/* G#2, A-2, A#2, B-2 */
856
,
/* C-3 */
};
static
const
float
multi_retrig_add
[]
=
{
0
.
f
,
-
1
.
f
,
-
2
.
f
,
-
4
.
f
,
/* 0, 1, 2, 3 */
-
8
.
f
,
-
16
.
f
,
0
.
f
,
0
.
f
,
/* 4, 5, 6, 7 */
0
.
f
,
1
.
f
,
2
.
f
,
4
.
f
,
/* 8, 9, A, B */
8
.
f
,
16
.
f
,
0
.
f
,
0
.
f
/* C, D, E, F */
};
static
const
float
multi_retrig_multiply
[]
=
{
1
.
f
,
1
.
f
,
1
.
f
,
1
.
f
,
/* 0, 1, 2, 3 */
1
.
f
,
1
.
f
,
.
6666667
f
,
.
5
f
,
/* 4, 5, 6, 7 */
1
.
f
,
1
.
f
,
1
.
f
,
1
.
f
,
/* 8, 9, A, B */
1
.
f
,
1
.
f
,
1
.
5
f
,
2
.
f
/* C, D, E, F */
};
#define jar_xm_CLAMP_UP1F(vol, limit) do { \
if((vol) > (limit)) (vol) = (limit); \
} while(0)
#define jar_xm_CLAMP_UP(vol) jar_xm_CLAMP_UP1F((vol), 1.f)
#define jar_xm_CLAMP_DOWN1F(vol, limit) do { \
if((vol) < (limit)) (vol) = (limit); \
} while(0)
#define jar_xm_CLAMP_DOWN(vol) jar_xm_CLAMP_DOWN1F((vol), .0f)
#define jar_xm_CLAMP2F(vol, up, down) do { \
if((vol) > (up)) (vol) = (up); \
else if((vol) < (down)) (vol) = (down); \
} while(0)
#define jar_xm_CLAMP(vol) jar_xm_CLAMP2F((vol), 1.f, .0f)
#define jar_xm_SLIDE_TOWARDS(val, goal, incr) do { \
if((val) > (goal)) { \
(val) -= (incr); \
jar_xm_CLAMP_DOWN1F((val), (goal)); \
} else if((val) < (goal)) { \
(val) += (incr); \
jar_xm_CLAMP_UP1F((val), (goal)); \
} \
} while(0)
#define jar_xm_LERP(u, v, t) ((u) + (t) * ((v) - (u)))
#define jar_xm_INVERSE_LERP(u, v, lerp) (((lerp) - (u)) / ((v) - (u)))
#define HAS_TONE_PORTAMENTO(s) ((s)->effect_type == 3 \
|| (s)->effect_type == 5 \
|| ((s)->volume_column >> 4) == 0xF)
#define HAS_ARPEGGIO(s) ((s)->effect_type == 0 \
&& (s)->effect_param != 0)
#define HAS_VIBRATO(s) ((s)->effect_type == 4 \
|| (s)->effect_param == 6 \
|| ((s)->volume_column >> 4) == 0xB)
#define NOTE_IS_VALID(n) ((n) > 0 && (n) < 97)
/* ----- Function definitions ----- */
static
float
jar_xm_waveform
(
jar_xm_waveform_type_t
waveform
,
uint8_t
step
)
{
static
unsigned
int
next_rand
=
24492
;
step
%=
0x40
;
switch
(
waveform
)
{
case
jar_xm_SINE_WAVEFORM
:
/* Why not use a table? For saving space, and because there's
* very very little actual performance gain. */
return
-
sinf
(
2
.
f
*
3
.
141592
f
*
(
float
)
step
/
(
float
)
0x40
);
case
jar_xm_RAMP_DOWN_WAVEFORM
:
/* Ramp down: 1.0f when step = 0; -1.0f when step = 0x40 */
return
(
float
)(
0x20
-
step
)
/
0x20
;
case
jar_xm_SQUARE_WAVEFORM
:
/* Square with a 50% duty */
return
(
step
>=
0x20
)
?
1
.
f
:
-
1
.
f
;
case
jar_xm_RANDOM_WAVEFORM
:
/* Use the POSIX.1-2001 example, just to be deterministic
* across different machines */
next_rand
=
next_rand
*
1103515245
+
12345
;
return
(
float
)((
next_rand
>>
16
)
&
0x7FFF
)
/
(
float
)
0x4000
-
1
.
f
;
case
jar_xm_RAMP_UP_WAVEFORM
:
/* Ramp up: -1.f when step = 0; 1.f when step = 0x40 */
return
(
float
)(
step
-
0x20
)
/
0x20
;
default:
break
;
}
return
.
0
f
;
}
static
void
jar_xm_autovibrato
(
jar_xm_context_t
*
ctx
,
jar_xm_channel_context_t
*
ch
)
{
if
(
ch
->
instrument
==
NULL
||
ch
->
instrument
->
vibrato_depth
==
0
)
return
;
jar_xm_instrument_t
*
instr
=
ch
->
instrument
;
float
sweep
=
1
.
f
;
if
(
ch
->
autovibrato_ticks
<
instr
->
vibrato_sweep
)
{
/* No idea if this is correct, but it sounds close enough… */
sweep
=
jar_xm_LERP
(
0
.
f
,
1
.
f
,
(
float
)
ch
->
autovibrato_ticks
/
(
float
)
instr
->
vibrato_sweep
);
}
unsigned
int
step
=
((
ch
->
autovibrato_ticks
++
)
*
instr
->
vibrato_rate
)
>>
2
;
ch
->
autovibrato_note_offset
=
.
25
f
*
jar_xm_waveform
(
instr
->
vibrato_type
,
step
)
*
(
float
)
instr
->
vibrato_depth
/
(
float
)
0xF
*
sweep
;
jar_xm_update_frequency
(
ctx
,
ch
);
}
static
void
jar_xm_vibrato
(
jar_xm_context_t
*
ctx
,
jar_xm_channel_context_t
*
ch
,
uint8_t
param
,
uint16_t
pos
)
{
unsigned
int
step
=
pos
*
(
param
>>
4
);
ch
->
vibrato_note_offset
=
2
.
f
*
jar_xm_waveform
(
ch
->
vibrato_waveform
,
step
)
*
(
float
)(
param
&
0x0F
)
/
(
float
)
0xF
;
jar_xm_update_frequency
(
ctx
,
ch
);
}
static
void
jar_xm_tremolo
(
jar_xm_context_t
*
ctx
,
jar_xm_channel_context_t
*
ch
,
uint8_t
param
,
uint16_t
pos
)
{
unsigned
int
step
=
pos
*
(
param
>>
4
);
/* Not so sure about this, it sounds correct by ear compared with
* MilkyTracker, but it could come from other bugs */
ch
->
tremolo_volume
=
-
1
.
f
*
jar_xm_waveform
(
ch
->
tremolo_waveform
,
step
)
*
(
float
)(
param
&
0x0F
)
/
(
float
)
0xF
;
}
static
void
jar_xm_arpeggio
(
jar_xm_context_t
*
ctx
,
jar_xm_channel_context_t
*
ch
,
uint8_t
param
,
uint16_t
tick
)
{
switch
(
tick
%
3
)
{
case
0
:
ch
->
arp_in_progress
=
false
;
ch
->
arp_note_offset
=
0
;
break
;
case
2
:
ch
->
arp_in_progress
=
true
;
ch
->
arp_note_offset
=
param
>>
4
;
break
;
case
1
:
ch
->
arp_in_progress
=
true
;
ch
->
arp_note_offset
=
param
&
0x0F
;
break
;
}
jar_xm_update_frequency
(
ctx
,
ch
);
}
static
void
jar_xm_tone_portamento
(
jar_xm_context_t
*
ctx
,
jar_xm_channel_context_t
*
ch
)
{
/* 3xx called without a note, wait until we get an actual
* target note. */
if
(
ch
->
tone_portamento_target_period
==
0
.
f
)
return
;
if
(
ch
->
period
!=
ch
->
tone_portamento_target_period
)
{
jar_xm_SLIDE_TOWARDS
(
ch
->
period
,
ch
->
tone_portamento_target_period
,
(
ctx
->
module
.
frequency_type
==
jar_xm_LINEAR_FREQUENCIES
?
4
.
f
:
1
.
f
)
*
ch
->
tone_portamento_param
);
jar_xm_update_frequency
(
ctx
,
ch
);
}
}
static
void
jar_xm_pitch_slide
(
jar_xm_context_t
*
ctx
,
jar_xm_channel_context_t
*
ch
,
float
period_offset
)
{
/* Don't ask about the 4.f coefficient. I found mention of it
* nowhere. Found by ear™. */
if
(
ctx
->
module
.
frequency_type
==
jar_xm_LINEAR_FREQUENCIES
)
{
period_offset
*=
4
.
f
;
}
ch
->
period
+=
period_offset
;
jar_xm_CLAMP_DOWN
(
ch
->
period
);
/* XXX: upper bound of period ? */
jar_xm_update_frequency
(
ctx
,
ch
);
}
static
void
jar_xm_panning_slide
(
jar_xm_channel_context_t
*
ch
,
uint8_t
rawval
)
{
float
f
;
if
((
rawval
&
0xF0
)
&&
(
rawval
&
0x0F
))
{
/* Illegal state */
return
;
}
if
(
rawval
&
0xF0
)
{
/* Slide right */
f
=
(
float
)(
rawval
>>
4
)
/
(
float
)
0xFF
;
ch
->
panning
+=
f
;
jar_xm_CLAMP_UP
(
ch
->
panning
);
}
else
{
/* Slide left */
f
=
(
float
)(
rawval
&
0x0F
)
/
(
float
)
0xFF
;
ch
->
panning
-=
f
;
jar_xm_CLAMP_DOWN
(
ch
->
panning
);
}
}
static
void
jar_xm_volume_slide
(
jar_xm_channel_context_t
*
ch
,
uint8_t
rawval
)
{
float
f
;
if
((
rawval
&
0xF0
)
&&
(
rawval
&
0x0F
))
{
/* Illegal state */
return
;
}
if
(
rawval
&
0xF0
)
{
/* Slide up */
f
=
(
float
)(
rawval
>>
4
)
/
(
float
)
0x40
;
ch
->
volume
+=
f
;
jar_xm_CLAMP_UP
(
ch
->
volume
);
}
else
{
/* Slide down */
f
=
(
float
)(
rawval
&
0x0F
)
/
(
float
)
0x40
;
ch
->
volume
-=
f
;
jar_xm_CLAMP_DOWN
(
ch
->
volume
);
}
}
static
float
jar_xm_envelope_lerp
(
jar_xm_envelope_point_t
*
restrict
a
,
jar_xm_envelope_point_t
*
restrict
b
,
uint16_t
pos
)
{
/* Linear interpolation between two envelope points */
if
(
pos
<=
a
->
frame
)
return
a
->
value
;
else
if
(
pos
>=
b
->
frame
)
return
b
->
value
;
else
{
float
p
=
(
float
)(
pos
-
a
->
frame
)
/
(
float
)(
b
->
frame
-
a
->
frame
);
return
a
->
value
*
(
1
-
p
)
+
b
->
value
*
p
;
}
}
static
void
jar_xm_post_pattern_change
(
jar_xm_context_t
*
ctx
)
{
/* Loop if necessary */
if
(
ctx
->
current_table_index
>=
ctx
->
module
.
length
)
{
ctx
->
current_table_index
=
ctx
->
module
.
restart_position
;
}
}
static
float
jar_xm_linear_period
(
float
note
)
{
return
7680
.
f
-
note
*
64
.
f
;
}
static
float
jar_xm_linear_frequency
(
float
period
)
{
return
8363
.
f
*
powf
(
2
.
f
,
(
4608
.
f
-
period
)
/
768
.
f
);
}
static
float
jar_xm_amiga_period
(
float
note
)
{
unsigned
int
intnote
=
note
;
uint8_t
a
=
intnote
%
12
;
int8_t
octave
=
note
/
12
.
f
-
2
;
uint16_t
p1
=
amiga_frequencies
[
a
],
p2
=
amiga_frequencies
[
a
+
1
];
if
(
octave
>
0
)
{
p1
>>=
octave
;
p2
>>=
octave
;
}
else
if
(
octave
<
0
)
{
p1
<<=
(
-
octave
);
p2
<<=
(
-
octave
);
}
return
jar_xm_LERP
(
p1
,
p2
,
note
-
intnote
);
}
static
float
jar_xm_amiga_frequency
(
float
period
)
{
if
(
period
==
.
0
f
)
return
.
0
f
;
/* This is the PAL value. No reason to choose this one over the
* NTSC value. */
return
7093789
.
2
f
/
(
period
*
2
.
f
);
}
static
float
jar_xm_period
(
jar_xm_context_t
*
ctx
,
float
note
)
{
switch
(
ctx
->
module
.
frequency_type
)
{
case
jar_xm_LINEAR_FREQUENCIES
:
return
jar_xm_linear_period
(
note
);
case
jar_xm_AMIGA_FREQUENCIES
:
return
jar_xm_amiga_period
(
note
);
}
return
.
0
f
;
}
static
float
jar_xm_frequency
(
jar_xm_context_t
*
ctx
,
float
period
,
float
note_offset
)
{
uint8_t
a
;
int8_t
octave
;
float
note
;
uint16_t
p1
,
p2
;
switch
(
ctx
->
module
.
frequency_type
)
{
case
jar_xm_LINEAR_FREQUENCIES
:
return
jar_xm_linear_frequency
(
period
-
64
.
f
*
note_offset
);
case
jar_xm_AMIGA_FREQUENCIES
:
if
(
note_offset
==
0
)
{
/* A chance to escape from insanity */
return
jar_xm_amiga_frequency
(
period
);
}
/* FIXME: this is very crappy at best */
a
=
octave
=
0
;
/* Find the octave of the current period */
if
(
period
>
amiga_frequencies
[
0
])
{
--
octave
;
while
(
period
>
(
amiga_frequencies
[
0
]
<<
(
-
octave
)))
--
octave
;
}
else
if
(
period
<
amiga_frequencies
[
12
])
{
++
octave
;
while
(
period
<
(
amiga_frequencies
[
12
]
>>
octave
))
++
octave
;
}
/* Find the smallest note closest to the current period */
for
(
uint8_t
i
=
0
;
i
<
12
;
++
i
)
{
p1
=
amiga_frequencies
[
i
],
p2
=
amiga_frequencies
[
i
+
1
];
if
(
octave
>
0
)
{
p1
>>=
octave
;
p2
>>=
octave
;
}
else
if
(
octave
<
0
)
{
p1
<<=
(
-
octave
);
p2
<<=
(
-
octave
);
}
if
(
p2
<=
period
&&
period
<=
p1
)
{
a
=
i
;
break
;
}
}
if
(
JAR_XM_DEBUG
&&
(
p1
<
period
||
p2
>
period
))
{
DEBUG
(
"%i <= %f <= %i should hold but doesn't, this is a bug"
,
p2
,
period
,
p1
);
}
note
=
12
.
f
*
(
octave
+
2
)
+
a
+
jar_xm_INVERSE_LERP
(
p1
,
p2
,
period
);
return
jar_xm_amiga_frequency
(
jar_xm_amiga_period
(
note
+
note_offset
));
}
return
.
0
f
;
}
static
void
jar_xm_update_frequency
(
jar_xm_context_t
*
ctx
,
jar_xm_channel_context_t
*
ch
)
{
ch
->
frequency
=
jar_xm_frequency
(
ctx
,
ch
->
period
,
(
ch
->
arp_note_offset
>
0
?
ch
->
arp_note_offset
:
(
ch
->
vibrato_note_offset
+
ch
->
autovibrato_note_offset
))
);
ch
->
step
=
ch
->
frequency
/
ctx
->
rate
;
}
static
void
jar_xm_handle_note_and_instrument
(
jar_xm_context_t
*
ctx
,
jar_xm_channel_context_t
*
ch
,
jar_xm_pattern_slot_t
*
s
)
{
if
(
s
->
instrument
>
0
)
{
if
(
HAS_TONE_PORTAMENTO
(
ch
->
current
)
&&
ch
->
instrument
!=
NULL
&&
ch
->
sample
!=
NULL
)
{
/* Tone portamento in effect, unclear stuff happens */
jar_xm_trigger_note
(
ctx
,
ch
,
jar_xm_TRIGGER_KEEP_PERIOD
|
jar_xm_TRIGGER_KEEP_SAMPLE_POSITION
);
}
else
if
(
s
->
instrument
>
ctx
->
module
.
num_instruments
)
{
/* Invalid instrument, Cut current note */
jar_xm_cut_note
(
ch
);
ch
->
instrument
=
NULL
;
ch
->
sample
=
NULL
;
}
else
{
ch
->
instrument
=
ctx
->
module
.
instruments
+
(
s
->
instrument
-
1
);
if
(
s
->
note
==
0
&&
ch
->
sample
!=
NULL
)
{
/* Ghost instrument, trigger note */
/* Sample position is kept, but envelopes are reset */
jar_xm_trigger_note
(
ctx
,
ch
,
jar_xm_TRIGGER_KEEP_SAMPLE_POSITION
);
}
}
}
if
(
NOTE_IS_VALID
(
s
->
note
))
{
/* Yes, the real note number is s->note -1. Try finding
* THAT in any of the specs! :-) */
jar_xm_instrument_t
*
instr
=
ch
->
instrument
;
if
(
HAS_TONE_PORTAMENTO
(
ch
->
current
)
&&
instr
!=
NULL
&&
ch
->
sample
!=
NULL
)
{
/* Tone portamento in effect */
ch
->
note
=
s
->
note
+
ch
->
sample
->
relative_note
+
ch
->
sample
->
finetune
/
128
.
f
-
1
.
f
;
ch
->
tone_portamento_target_period
=
jar_xm_period
(
ctx
,
ch
->
note
);
}
else
if
(
instr
==
NULL
||
ch
->
instrument
->
num_samples
==
0
)
{
/* Bad instrument */
jar_xm_cut_note
(
ch
);
}
else
{
if
(
instr
->
sample_of_notes
[
s
->
note
-
1
]
<
instr
->
num_samples
)
{
#if JAR_XM_RAMPING
for
(
unsigned
int
z
=
0
;
z
<
jar_xm_SAMPLE_RAMPING_POINTS
;
++
z
)
{
ch
->
end_of_previous_sample
[
z
]
=
jar_xm_next_of_sample
(
ch
);
}
ch
->
frame_count
=
0
;
#endif
ch
->
sample
=
instr
->
samples
+
instr
->
sample_of_notes
[
s
->
note
-
1
];
ch
->
orig_note
=
ch
->
note
=
s
->
note
+
ch
->
sample
->
relative_note
+
ch
->
sample
->
finetune
/
128
.
f
-
1
.
f
;
if
(
s
->
instrument
>
0
)
{
jar_xm_trigger_note
(
ctx
,
ch
,
0
);
}
else
{
/* Ghost note: keep old volume */
jar_xm_trigger_note
(
ctx
,
ch
,
jar_xm_TRIGGER_KEEP_VOLUME
);
}
}
else
{
/* Bad sample */
jar_xm_cut_note
(
ch
);
}
}
}
else
if
(
s
->
note
==
97
)
{
/* Key Off */
jar_xm_key_off
(
ch
);
}
switch
(
s
->
volume_column
>>
4
)
{
case
0x5
:
if
(
s
->
volume_column
>
0x50
)
break
;
case
0x1
:
case
0x2
:
case
0x3
:
case
0x4
:
/* Set volume */
ch
->
volume
=
(
float
)(
s
->
volume_column
-
0x10
)
/
(
float
)
0x40
;
break
;
case
0x8
:
/* Fine volume slide down */
jar_xm_volume_slide
(
ch
,
s
->
volume_column
&
0x0F
);
break
;
case
0x9
:
/* Fine volume slide up */
jar_xm_volume_slide
(
ch
,
s
->
volume_column
<<
4
);
break
;
case
0xA
:
/* Set vibrato speed */
ch
->
vibrato_param
=
(
ch
->
vibrato_param
&
0x0F
)
|
((
s
->
volume_column
&
0x0F
)
<<
4
);
break
;
case
0xC
:
/* Set panning */
ch
->
panning
=
(
float
)(
((
s
->
volume_column
&
0x0F
)
<<
4
)
|
(
s
->
volume_column
&
0x0F
)
)
/
(
float
)
0xFF
;
break
;
case
0xF
:
/* Tone portamento */
if
(
s
->
volume_column
&
0x0F
)
{
ch
->
tone_portamento_param
=
((
s
->
volume_column
&
0x0F
)
<<
4
)
|
(
s
->
volume_column
&
0x0F
);
}
break
;
default:
break
;
}
switch
(
s
->
effect_type
)
{
case
1
:
/* 1xx: Portamento up */
if
(
s
->
effect_param
>
0
)
{
ch
->
portamento_up_param
=
s
->
effect_param
;
}
break
;
case
2
:
/* 2xx: Portamento down */
if
(
s
->
effect_param
>
0
)
{
ch
->
portamento_down_param
=
s
->
effect_param
;
}
break
;
case
3
:
/* 3xx: Tone portamento */
if
(
s
->
effect_param
>
0
)
{
ch
->
tone_portamento_param
=
s
->
effect_param
;
}
break
;
case
4
:
/* 4xy: Vibrato */
if
(
s
->
effect_param
&
0x0F
)
{
/* Set vibrato depth */
ch
->
vibrato_param
=
(
ch
->
vibrato_param
&
0xF0
)
|
(
s
->
effect_param
&
0x0F
);
}
if
(
s
->
effect_param
>>
4
)
{
/* Set vibrato speed */
ch
->
vibrato_param
=
(
s
->
effect_param
&
0xF0
)
|
(
ch
->
vibrato_param
&
0x0F
);
}
break
;
case
5
:
/* 5xy: Tone portamento + Volume slide */
if
(
s
->
effect_param
>
0
)
{
ch
->
volume_slide_param
=
s
->
effect_param
;
}
break
;
case
6
:
/* 6xy: Vibrato + Volume slide */
if
(
s
->
effect_param
>
0
)
{
ch
->
volume_slide_param
=
s
->
effect_param
;
}
break
;
case
7
:
/* 7xy: Tremolo */
if
(
s
->
effect_param
&
0x0F
)
{
/* Set tremolo depth */
ch
->
tremolo_param
=
(
ch
->
tremolo_param
&
0xF0
)
|
(
s
->
effect_param
&
0x0F
);
}
if
(
s
->
effect_param
>>
4
)
{
/* Set tremolo speed */
ch
->
tremolo_param
=
(
s
->
effect_param
&
0xF0
)
|
(
ch
->
tremolo_param
&
0x0F
);
}
break
;
case
8
:
/* 8xx: Set panning */
ch
->
panning
=
(
float
)
s
->
effect_param
/
(
float
)
0xFF
;
break
;
case
9
:
/* 9xx: Sample offset */
if
(
ch
->
sample
!=
NULL
&&
NOTE_IS_VALID
(
s
->
note
))
{
uint32_t
final_offset
=
s
->
effect_param
<<
(
ch
->
sample
->
bits
==
16
?
7
:
8
);
if
(
final_offset
>=
ch
->
sample
->
length
)
{
/* Pretend the sample dosen't loop and is done playing */
ch
->
sample_position
=
-
1
;
break
;
}
ch
->
sample_position
=
final_offset
;
}
break
;
case
0xA
:
/* Axy: Volume slide */
if
(
s
->
effect_param
>
0
)
{
ch
->
volume_slide_param
=
s
->
effect_param
;
}
break
;
case
0xB
:
/* Bxx: Position jump */
if
(
s
->
effect_param
<
ctx
->
module
.
length
)
{
ctx
->
position_jump
=
true
;
ctx
->
jump_dest
=
s
->
effect_param
;
}
break
;
case
0xC
:
/* Cxx: Set volume */
ch
->
volume
=
(
float
)((
s
->
effect_param
>
0x40
)
?
0x40
:
s
->
effect_param
)
/
(
float
)
0x40
;
break
;
case
0xD
:
/* Dxx: Pattern break */
/* Jump after playing this line */
ctx
->
pattern_break
=
true
;
ctx
->
jump_row
=
(
s
->
effect_param
>>
4
)
*
10
+
(
s
->
effect_param
&
0x0F
);
break
;
case
0xE
:
/* EXy: Extended command */
switch
(
s
->
effect_param
>>
4
)
{
case
1
:
/* E1y: Fine portamento up */
if
(
s
->
effect_param
&
0x0F
)
{
ch
->
fine_portamento_up_param
=
s
->
effect_param
&
0x0F
;
}
jar_xm_pitch_slide
(
ctx
,
ch
,
-
ch
->
fine_portamento_up_param
);
break
;
case
2
:
/* E2y: Fine portamento down */
if
(
s
->
effect_param
&
0x0F
)
{
ch
->
fine_portamento_down_param
=
s
->
effect_param
&
0x0F
;
}
jar_xm_pitch_slide
(
ctx
,
ch
,
ch
->
fine_portamento_down_param
);
break
;
case
4
:
/* E4y: Set vibrato control */
ch
->
vibrato_waveform
=
s
->
effect_param
&
3
;
ch
->
vibrato_waveform_retrigger
=
!
((
s
->
effect_param
>>
2
)
&
1
);
break
;
case
5
:
/* E5y: Set finetune */
if
(
NOTE_IS_VALID
(
ch
->
current
->
note
)
&&
ch
->
sample
!=
NULL
)
{
ch
->
note
=
ch
->
current
->
note
+
ch
->
sample
->
relative_note
+
(
float
)(((
s
->
effect_param
&
0x0F
)
-
8
)
<<
4
)
/
128
.
f
-
1
.
f
;
ch
->
period
=
jar_xm_period
(
ctx
,
ch
->
note
);
jar_xm_update_frequency
(
ctx
,
ch
);
}
break
;
case
6
:
/* E6y: Pattern loop */
if
(
s
->
effect_param
&
0x0F
)
{
if
((
s
->
effect_param
&
0x0F
)
==
ch
->
pattern_loop_count
)
{
/* Loop is over */
ch
->
pattern_loop_count
=
0
;
break
;
}
/* Jump to the beginning of the loop */
ch
->
pattern_loop_count
++
;
ctx
->
position_jump
=
true
;
ctx
->
jump_row
=
ch
->
pattern_loop_origin
;
ctx
->
jump_dest
=
ctx
->
current_table_index
;
}
else
{
/* Set loop start point */
ch
->
pattern_loop_origin
=
ctx
->
current_row
;
/* Replicate FT2 E60 bug */
ctx
->
jump_row
=
ch
->
pattern_loop_origin
;
}
break
;
case
7
:
/* E7y: Set tremolo control */
ch
->
tremolo_waveform
=
s
->
effect_param
&
3
;
ch
->
tremolo_waveform_retrigger
=
!
((
s
->
effect_param
>>
2
)
&
1
);
break
;
case
0xA
:
/* EAy: Fine volume slide up */
if
(
s
->
effect_param
&
0x0F
)
{
ch
->
fine_volume_slide_param
=
s
->
effect_param
&
0x0F
;
}
jar_xm_volume_slide
(
ch
,
ch
->
fine_volume_slide_param
<<
4
);
break
;
case
0xB
:
/* EBy: Fine volume slide down */
if
(
s
->
effect_param
&
0x0F
)
{
ch
->
fine_volume_slide_param
=
s
->
effect_param
&
0x0F
;
}
jar_xm_volume_slide
(
ch
,
ch
->
fine_volume_slide_param
);
break
;
case
0xD
:
/* EDy: Note delay */
/* XXX: figure this out better. EDx triggers
* the note even when there no note and no
* instrument. But ED0 acts like like a ghost
* note, EDx (x ≠ 0) does not. */
if
(
s
->
note
==
0
&&
s
->
instrument
==
0
)
{
unsigned
int
flags
=
jar_xm_TRIGGER_KEEP_VOLUME
;
if
(
ch
->
current
->
effect_param
&
0x0F
)
{
ch
->
note
=
ch
->
orig_note
;
jar_xm_trigger_note
(
ctx
,
ch
,
flags
);
}
else
{
jar_xm_trigger_note
(
ctx
,
ch
,
flags
|
jar_xm_TRIGGER_KEEP_PERIOD
|
jar_xm_TRIGGER_KEEP_SAMPLE_POSITION
);
}
}
break
;
case
0xE
:
/* EEy: Pattern delay */
ctx
->
extra_ticks
=
(
ch
->
current
->
effect_param
&
0x0F
)
*
ctx
->
tempo
;
break
;
default:
break
;
}
break
;
case
0xF
:
/* Fxx: Set tempo/BPM */
if
(
s
->
effect_param
>
0
)
{
if
(
s
->
effect_param
<=
0x1F
)
{
ctx
->
tempo
=
s
->
effect_param
;
}
else
{
ctx
->
bpm
=
s
->
effect_param
;
}
}
break
;
case
16
:
/* Gxx: Set global volume */
ctx
->
global_volume
=
(
float
)((
s
->
effect_param
>
0x40
)
?
0x40
:
s
->
effect_param
)
/
(
float
)
0x40
;
break
;
case
17
:
/* Hxy: Global volume slide */
if
(
s
->
effect_param
>
0
)
{
ch
->
global_volume_slide_param
=
s
->
effect_param
;
}
break
;
case
21
:
/* Lxx: Set envelope position */
ch
->
volume_envelope_frame_count
=
s
->
effect_param
;
ch
->
panning_envelope_frame_count
=
s
->
effect_param
;
break
;
case
25
:
/* Pxy: Panning slide */
if
(
s
->
effect_param
>
0
)
{
ch
->
panning_slide_param
=
s
->
effect_param
;
}
break
;
case
27
:
/* Rxy: Multi retrig note */
if
(
s
->
effect_param
>
0
)
{
if
((
s
->
effect_param
>>
4
)
==
0
)
{
/* Keep previous x value */
ch
->
multi_retrig_param
=
(
ch
->
multi_retrig_param
&
0xF0
)
|
(
s
->
effect_param
&
0x0F
);
}
else
{
ch
->
multi_retrig_param
=
s
->
effect_param
;
}
}
break
;
case
29
:
/* Txy: Tremor */
if
(
s
->
effect_param
>
0
)
{
/* Tremor x and y params do not appear to be separately
* kept in memory, unlike Rxy */
ch
->
tremor_param
=
s
->
effect_param
;
}
break
;
case
33
:
/* Xxy: Extra stuff */
switch
(
s
->
effect_param
>>
4
)
{
case
1
:
/* X1y: Extra fine portamento up */
if
(
s
->
effect_param
&
0x0F
)
{
ch
->
extra_fine_portamento_up_param
=
s
->
effect_param
&
0x0F
;
}
jar_xm_pitch_slide
(
ctx
,
ch
,
-
1
.
0
f
*
ch
->
extra_fine_portamento_up_param
);
break
;
case
2
:
/* X2y: Extra fine portamento down */
if
(
s
->
effect_param
&
0x0F
)
{
ch
->
extra_fine_portamento_down_param
=
s
->
effect_param
&
0x0F
;
}
jar_xm_pitch_slide
(
ctx
,
ch
,
ch
->
extra_fine_portamento_down_param
);
break
;
default:
break
;
}
break
;
default:
break
;
}
}
static
void
jar_xm_trigger_note
(
jar_xm_context_t
*
ctx
,
jar_xm_channel_context_t
*
ch
,
unsigned
int
flags
)
{
if
(
!
(
flags
&
jar_xm_TRIGGER_KEEP_SAMPLE_POSITION
))
{
ch
->
sample_position
=
0
.
f
;
ch
->
ping
=
true
;
}
if
(
ch
->
sample
!=
NULL
)
{
if
(
!
(
flags
&
jar_xm_TRIGGER_KEEP_VOLUME
))
{
ch
->
volume
=
ch
->
sample
->
volume
;
}
ch
->
panning
=
ch
->
sample
->
panning
;
}
ch
->
sustained
=
true
;
ch
->
fadeout_volume
=
ch
->
volume_envelope_volume
=
1
.
0
f
;
ch
->
panning_envelope_panning
=
.
5
f
;
ch
->
volume_envelope_frame_count
=
ch
->
panning_envelope_frame_count
=
0
;
ch
->
vibrato_note_offset
=
0
.
f
;
ch
->
tremolo_volume
=
0
.
f
;
ch
->
tremor_on
=
false
;
ch
->
autovibrato_ticks
=
0
;
if
(
ch
->
vibrato_waveform_retrigger
)
{
ch
->
vibrato_ticks
=
0
;
/* XXX: should the waveform itself also
* be reset to sine? */
}
if
(
ch
->
tremolo_waveform_retrigger
)
{
ch
->
tremolo_ticks
=
0
;
}
if
(
!
(
flags
&
jar_xm_TRIGGER_KEEP_PERIOD
))
{
ch
->
period
=
jar_xm_period
(
ctx
,
ch
->
note
);
jar_xm_update_frequency
(
ctx
,
ch
);
}
ch
->
latest_trigger
=
ctx
->
generated_samples
;
if
(
ch
->
instrument
!=
NULL
)
{
ch
->
instrument
->
latest_trigger
=
ctx
->
generated_samples
;
}
if
(
ch
->
sample
!=
NULL
)
{
ch
->
sample
->
latest_trigger
=
ctx
->
generated_samples
;
}
}
static
void
jar_xm_cut_note
(
jar_xm_channel_context_t
*
ch
)
{
/* NB: this is not the same as Key Off */
ch
->
volume
=
.
0
f
;
}
static
void
jar_xm_key_off
(
jar_xm_channel_context_t
*
ch
)
{
/* Key Off */
ch
->
sustained
=
false
;
/* If no volume envelope is used, also cut the note */
if
(
ch
->
instrument
==
NULL
||
!
ch
->
instrument
->
volume_envelope
.
enabled
)
{
jar_xm_cut_note
(
ch
);
}
}
static
void
jar_xm_row
(
jar_xm_context_t
*
ctx
)
{
if
(
ctx
->
position_jump
)
{
ctx
->
current_table_index
=
ctx
->
jump_dest
;
ctx
->
current_row
=
ctx
->
jump_row
;
ctx
->
position_jump
=
false
;
ctx
->
pattern_break
=
false
;
ctx
->
jump_row
=
0
;
jar_xm_post_pattern_change
(
ctx
);
}
else
if
(
ctx
->
pattern_break
)
{
ctx
->
current_table_index
++
;
ctx
->
current_row
=
ctx
->
jump_row
;
ctx
->
pattern_break
=
false
;
ctx
->
jump_row
=
0
;
jar_xm_post_pattern_change
(
ctx
);
}
jar_xm_pattern_t
*
cur
=
ctx
->
module
.
patterns
+
ctx
->
module
.
pattern_table
[
ctx
->
current_table_index
];
bool
in_a_loop
=
false
;
/* Read notes… */
for
(
uint8_t
i
=
0
;
i
<
ctx
->
module
.
num_channels
;
++
i
)
{
jar_xm_pattern_slot_t
*
s
=
cur
->
slots
+
ctx
->
current_row
*
ctx
->
module
.
num_channels
+
i
;
jar_xm_channel_context_t
*
ch
=
ctx
->
channels
+
i
;
ch
->
current
=
s
;
if
(
s
->
effect_type
!=
0xE
||
s
->
effect_param
>>
4
!=
0xD
)
{
jar_xm_handle_note_and_instrument
(
ctx
,
ch
,
s
);
}
else
{
ch
->
note_delay_param
=
s
->
effect_param
&
0x0F
;
}
if
(
!
in_a_loop
&&
ch
->
pattern_loop_count
>
0
)
{
in_a_loop
=
true
;
}
}
if
(
!
in_a_loop
)
{
/* No E6y loop is in effect (or we are in the first pass) */
ctx
->
loop_count
=
(
ctx
->
row_loop_count
[
MAX_NUM_ROWS
*
ctx
->
current_table_index
+
ctx
->
current_row
]
++
);
}
ctx
->
current_row
++
;
/* Since this is an uint8, this line can
* increment from 255 to 0, in which case it
* is still necessary to go the next
* pattern. */
if
(
!
ctx
->
position_jump
&&
!
ctx
->
pattern_break
&&
(
ctx
->
current_row
>=
cur
->
num_rows
||
ctx
->
current_row
==
0
))
{
ctx
->
current_table_index
++
;
ctx
->
current_row
=
ctx
->
jump_row
;
/* This will be 0 most of
* the time, except when E60
* is used */
ctx
->
jump_row
=
0
;
jar_xm_post_pattern_change
(
ctx
);
}
}
static
void
jar_xm_envelope_tick
(
jar_xm_channel_context_t
*
ch
,
jar_xm_envelope_t
*
env
,
uint16_t
*
counter
,
float
*
outval
)
{
if
(
env
->
num_points
<
2
)
{
/* Don't really know what to do… */
if
(
env
->
num_points
==
1
)
{
/* XXX I am pulling this out of my ass */
*
outval
=
(
float
)
env
->
points
[
0
].
value
/
(
float
)
0x40
;
if
(
*
outval
>
1
)
{
*
outval
=
1
;
}
}
return
;
}
else
{
uint8_t
j
;
if
(
env
->
loop_enabled
)
{
uint16_t
loop_start
=
env
->
points
[
env
->
loop_start_point
].
frame
;
uint16_t
loop_end
=
env
->
points
[
env
->
loop_end_point
].
frame
;
uint16_t
loop_length
=
loop_end
-
loop_start
;
if
(
*
counter
>=
loop_end
)
{
*
counter
-=
loop_length
;
}
}
for
(
j
=
0
;
j
<
(
env
->
num_points
-
2
);
++
j
)
{
if
(
env
->
points
[
j
].
frame
<=
*
counter
&&
env
->
points
[
j
+
1
].
frame
>=
*
counter
)
{
break
;
}
}
*
outval
=
jar_xm_envelope_lerp
(
env
->
points
+
j
,
env
->
points
+
j
+
1
,
*
counter
)
/
(
float
)
0x40
;
/* Make sure it is safe to increment frame count */
if
(
!
ch
->
sustained
||
!
env
->
sustain_enabled
||
*
counter
!=
env
->
points
[
env
->
sustain_point
].
frame
)
{
(
*
counter
)
++
;
}
}
}
static
void
jar_xm_envelopes
(
jar_xm_channel_context_t
*
ch
)
{
if
(
ch
->
instrument
!=
NULL
)
{
if
(
ch
->
instrument
->
volume_envelope
.
enabled
)
{
if
(
!
ch
->
sustained
)
{
ch
->
fadeout_volume
-=
(
float
)
ch
->
instrument
->
volume_fadeout
/
65536
.
f
;
jar_xm_CLAMP_DOWN
(
ch
->
fadeout_volume
);
}
jar_xm_envelope_tick
(
ch
,
&
(
ch
->
instrument
->
volume_envelope
),
&
(
ch
->
volume_envelope_frame_count
),
&
(
ch
->
volume_envelope_volume
));
}
if
(
ch
->
instrument
->
panning_envelope
.
enabled
)
{
jar_xm_envelope_tick
(
ch
,
&
(
ch
->
instrument
->
panning_envelope
),
&
(
ch
->
panning_envelope_frame_count
),
&
(
ch
->
panning_envelope_panning
));
}
}
}
static
void
jar_xm_tick
(
jar_xm_context_t
*
ctx
)
{
if
(
ctx
->
current_tick
==
0
)
{
jar_xm_row
(
ctx
);
}
for
(
uint8_t
i
=
0
;
i
<
ctx
->
module
.
num_channels
;
++
i
)
{
jar_xm_channel_context_t
*
ch
=
ctx
->
channels
+
i
;
jar_xm_envelopes
(
ch
);
jar_xm_autovibrato
(
ctx
,
ch
);
if
(
ch
->
arp_in_progress
&&
!
HAS_ARPEGGIO
(
ch
->
current
))
{
ch
->
arp_in_progress
=
false
;
ch
->
arp_note_offset
=
0
;
jar_xm_update_frequency
(
ctx
,
ch
);
}
if
(
ch
->
vibrato_in_progress
&&
!
HAS_VIBRATO
(
ch
->
current
))
{
ch
->
vibrato_in_progress
=
false
;
ch
->
vibrato_note_offset
=
0
.
f
;
jar_xm_update_frequency
(
ctx
,
ch
);
}
switch
(
ch
->
current
->
volume_column
>>
4
)
{
case
0x6
:
/* Volume slide down */
if
(
ctx
->
current_tick
==
0
)
break
;
jar_xm_volume_slide
(
ch
,
ch
->
current
->
volume_column
&
0x0F
);
break
;
case
0x7
:
/* Volume slide up */
if
(
ctx
->
current_tick
==
0
)
break
;
jar_xm_volume_slide
(
ch
,
ch
->
current
->
volume_column
<<
4
);
break
;
case
0xB
:
/* Vibrato */
if
(
ctx
->
current_tick
==
0
)
break
;
ch
->
vibrato_in_progress
=
false
;
jar_xm_vibrato
(
ctx
,
ch
,
ch
->
vibrato_param
,
ch
->
vibrato_ticks
++
);
break
;
case
0xD
:
/* Panning slide left */
if
(
ctx
->
current_tick
==
0
)
break
;
jar_xm_panning_slide
(
ch
,
ch
->
current
->
volume_column
&
0x0F
);
break
;
case
0xE
:
/* Panning slide right */
if
(
ctx
->
current_tick
==
0
)
break
;
jar_xm_panning_slide
(
ch
,
ch
->
current
->
volume_column
<<
4
);
break
;
case
0xF
:
/* Tone portamento */
if
(
ctx
->
current_tick
==
0
)
break
;
jar_xm_tone_portamento
(
ctx
,
ch
);
break
;
default:
break
;
}
switch
(
ch
->
current
->
effect_type
)
{
case
0
:
/* 0xy: Arpeggio */
if
(
ch
->
current
->
effect_param
>
0
)
{
char
arp_offset
=
ctx
->
tempo
%
3
;
switch
(
arp_offset
)
{
case
2
:
/* 0 -> x -> 0 -> y -> x -> … */
if
(
ctx
->
current_tick
==
1
)
{
ch
->
arp_in_progress
=
true
;
ch
->
arp_note_offset
=
ch
->
current
->
effect_param
>>
4
;
jar_xm_update_frequency
(
ctx
,
ch
);
break
;
}
/* No break here, this is intended */
case
1
:
/* 0 -> 0 -> y -> x -> … */
if
(
ctx
->
current_tick
==
0
)
{
ch
->
arp_in_progress
=
false
;
ch
->
arp_note_offset
=
0
;
jar_xm_update_frequency
(
ctx
,
ch
);
break
;
}
/* No break here, this is intended */
case
0
:
/* 0 -> y -> x -> … */
jar_xm_arpeggio
(
ctx
,
ch
,
ch
->
current
->
effect_param
,
ctx
->
current_tick
-
arp_offset
);
default:
break
;
}
}
break
;
case
1
:
/* 1xx: Portamento up */
if
(
ctx
->
current_tick
==
0
)
break
;
jar_xm_pitch_slide
(
ctx
,
ch
,
-
ch
->
portamento_up_param
);
break
;
case
2
:
/* 2xx: Portamento down */
if
(
ctx
->
current_tick
==
0
)
break
;
jar_xm_pitch_slide
(
ctx
,
ch
,
ch
->
portamento_down_param
);
break
;
case
3
:
/* 3xx: Tone portamento */
if
(
ctx
->
current_tick
==
0
)
break
;
jar_xm_tone_portamento
(
ctx
,
ch
);
break
;
case
4
:
/* 4xy: Vibrato */
if
(
ctx
->
current_tick
==
0
)
break
;
ch
->
vibrato_in_progress
=
true
;
jar_xm_vibrato
(
ctx
,
ch
,
ch
->
vibrato_param
,
ch
->
vibrato_ticks
++
);
break
;
case
5
:
/* 5xy: Tone portamento + Volume slide */
if
(
ctx
->
current_tick
==
0
)
break
;
jar_xm_tone_portamento
(
ctx
,
ch
);
jar_xm_volume_slide
(
ch
,
ch
->
volume_slide_param
);
break
;
case
6
:
/* 6xy: Vibrato + Volume slide */
if
(
ctx
->
current_tick
==
0
)
break
;
ch
->
vibrato_in_progress
=
true
;
jar_xm_vibrato
(
ctx
,
ch
,
ch
->
vibrato_param
,
ch
->
vibrato_ticks
++
);
jar_xm_volume_slide
(
ch
,
ch
->
volume_slide_param
);
break
;
case
7
:
/* 7xy: Tremolo */
if
(
ctx
->
current_tick
==
0
)
break
;
jar_xm_tremolo
(
ctx
,
ch
,
ch
->
tremolo_param
,
ch
->
tremolo_ticks
++
);
break
;
case
0xA
:
/* Axy: Volume slide */
if
(
ctx
->
current_tick
==
0
)
break
;
jar_xm_volume_slide
(
ch
,
ch
->
volume_slide_param
);
break
;
case
0xE
:
/* EXy: Extended command */
switch
(
ch
->
current
->
effect_param
>>
4
)
{
case
0x9
:
/* E9y: Retrigger note */
if
(
ctx
->
current_tick
!=
0
&&
ch
->
current
->
effect_param
&
0x0F
)
{
if
(
!
(
ctx
->
current_tick
%
(
ch
->
current
->
effect_param
&
0x0F
)))
{
jar_xm_trigger_note
(
ctx
,
ch
,
0
);
jar_xm_envelopes
(
ch
);
}
}
break
;
case
0xC
:
/* ECy: Note cut */
if
((
ch
->
current
->
effect_param
&
0x0F
)
==
ctx
->
current_tick
)
{
jar_xm_cut_note
(
ch
);
}
break
;
case
0xD
:
/* EDy: Note delay */
if
(
ch
->
note_delay_param
==
ctx
->
current_tick
)
{
jar_xm_handle_note_and_instrument
(
ctx
,
ch
,
ch
->
current
);
jar_xm_envelopes
(
ch
);
}
break
;
default:
break
;
}
break
;
case
17
:
/* Hxy: Global volume slide */
if
(
ctx
->
current_tick
==
0
)
break
;
if
((
ch
->
global_volume_slide_param
&
0xF0
)
&&
(
ch
->
global_volume_slide_param
&
0x0F
))
{
/* Illegal state */
break
;
}
if
(
ch
->
global_volume_slide_param
&
0xF0
)
{
/* Global slide up */
float
f
=
(
float
)(
ch
->
global_volume_slide_param
>>
4
)
/
(
float
)
0x40
;
ctx
->
global_volume
+=
f
;
jar_xm_CLAMP_UP
(
ctx
->
global_volume
);
}
else
{
/* Global slide down */
float
f
=
(
float
)(
ch
->
global_volume_slide_param
&
0x0F
)
/
(
float
)
0x40
;
ctx
->
global_volume
-=
f
;
jar_xm_CLAMP_DOWN
(
ctx
->
global_volume
);
}
break
;
case
20
:
/* Kxx: Key off */
/* Most documentations will tell you the parameter has no
* use. Don't be fooled. */
if
(
ctx
->
current_tick
==
ch
->
current
->
effect_param
)
{
jar_xm_key_off
(
ch
);
}
break
;
case
25
:
/* Pxy: Panning slide */
if
(
ctx
->
current_tick
==
0
)
break
;
jar_xm_panning_slide
(
ch
,
ch
->
panning_slide_param
);
break
;
case
27
:
/* Rxy: Multi retrig note */
if
(
ctx
->
current_tick
==
0
)
break
;
if
(((
ch
->
multi_retrig_param
)
&
0x0F
)
==
0
)
break
;
if
((
ctx
->
current_tick
%
(
ch
->
multi_retrig_param
&
0x0F
))
==
0
)
{
float
v
=
ch
->
volume
*
multi_retrig_multiply
[
ch
->
multi_retrig_param
>>
4
]
+
multi_retrig_add
[
ch
->
multi_retrig_param
>>
4
];
jar_xm_CLAMP
(
v
);
jar_xm_trigger_note
(
ctx
,
ch
,
0
);
ch
->
volume
=
v
;
}
break
;
case
29
:
/* Txy: Tremor */
if
(
ctx
->
current_tick
==
0
)
break
;
ch
->
tremor_on
=
(
(
ctx
->
current_tick
-
1
)
%
((
ch
->
tremor_param
>>
4
)
+
(
ch
->
tremor_param
&
0x0F
)
+
2
)
>
(
ch
->
tremor_param
>>
4
)
);
break
;
default:
break
;
}
float
panning
,
volume
;
panning
=
ch
->
panning
+
(
ch
->
panning_envelope_panning
-
.
5
f
)
*
(.
5
f
-
fabsf
(
ch
->
panning
-
.
5
f
))
*
2
.
0
f
;
if
(
ch
->
tremor_on
)
{
volume
=
.
0
f
;
}
else
{
volume
=
ch
->
volume
+
ch
->
tremolo_volume
;
jar_xm_CLAMP
(
volume
);
volume
*=
ch
->
fadeout_volume
*
ch
->
volume_envelope_volume
;
}
#if JAR_XM_RAMPING
ch
->
target_panning
=
panning
;
ch
->
target_volume
=
volume
;
#else
ch
->
actual_panning
=
panning
;
ch
->
actual_volume
=
volume
;
#endif
}
ctx
->
current_tick
++
;
if
(
ctx
->
current_tick
>=
ctx
->
tempo
+
ctx
->
extra_ticks
)
{
ctx
->
current_tick
=
0
;
ctx
->
extra_ticks
=
0
;
}
/* FT2 manual says number of ticks / second = BPM * 0.4 */
ctx
->
remaining_samples_in_tick
+=
(
float
)
ctx
->
rate
/
((
float
)
ctx
->
bpm
*
0
.
4
f
);
}
static
float
jar_xm_next_of_sample
(
jar_xm_channel_context_t
*
ch
)
{
if
(
ch
->
instrument
==
NULL
||
ch
->
sample
==
NULL
||
ch
->
sample_position
<
0
)
{
#if JAR_XM_RAMPING
if
(
ch
->
frame_count
<
jar_xm_SAMPLE_RAMPING_POINTS
)
{
return
jar_xm_LERP
(
ch
->
end_of_previous_sample
[
ch
->
frame_count
],
.
0
f
,
(
float
)
ch
->
frame_count
/
(
float
)
jar_xm_SAMPLE_RAMPING_POINTS
);
}
#endif
return
.
0
f
;
}
if
(
ch
->
sample
->
length
==
0
)
{
return
.
0
f
;
}
float
u
,
v
,
t
;
uint32_t
a
,
b
;
a
=
(
uint32_t
)
ch
->
sample_position
;
/* This cast is fine,
* sample_position will not
* go above integer
* ranges */
if
(
JAR_XM_LINEAR_INTERPOLATION
)
{
b
=
a
+
1
;
t
=
ch
->
sample_position
-
a
;
/* Cheaper than fmodf(., 1.f) */
}
u
=
ch
->
sample
->
data
[
a
];
switch
(
ch
->
sample
->
loop_type
)
{
case
jar_xm_NO_LOOP
:
if
(
JAR_XM_LINEAR_INTERPOLATION
)
{
v
=
(
b
<
ch
->
sample
->
length
)
?
ch
->
sample
->
data
[
b
]
:
.
0
f
;
}
ch
->
sample_position
+=
ch
->
step
;
if
(
ch
->
sample_position
>=
ch
->
sample
->
length
)
{
ch
->
sample_position
=
-
1
;
}
break
;
case
jar_xm_FORWARD_LOOP
:
if
(
JAR_XM_LINEAR_INTERPOLATION
)
{
v
=
ch
->
sample
->
data
[
(
b
==
ch
->
sample
->
loop_end
)
?
ch
->
sample
->
loop_start
:
b
];
}
ch
->
sample_position
+=
ch
->
step
;
while
(
ch
->
sample_position
>=
ch
->
sample
->
loop_end
)
{
ch
->
sample_position
-=
ch
->
sample
->
loop_length
;
}
break
;
case
jar_xm_PING_PONG_LOOP
:
if
(
ch
->
ping
)
{
ch
->
sample_position
+=
ch
->
step
;
}
else
{
ch
->
sample_position
-=
ch
->
step
;
}
/* XXX: this may not work for very tight ping-pong loops
* (ie switches direction more than once per sample */
if
(
ch
->
ping
)
{
if
(
JAR_XM_LINEAR_INTERPOLATION
)
{
v
=
(
b
>=
ch
->
sample
->
loop_end
)
?
ch
->
sample
->
data
[
a
]
:
ch
->
sample
->
data
[
b
];
}
if
(
ch
->
sample_position
>=
ch
->
sample
->
loop_end
)
{
ch
->
ping
=
false
;
ch
->
sample_position
=
(
ch
->
sample
->
loop_end
<<
1
)
-
ch
->
sample_position
;
}
/* sanity checking */
if
(
ch
->
sample_position
>=
ch
->
sample
->
length
)
{
ch
->
ping
=
false
;
ch
->
sample_position
-=
ch
->
sample
->
length
-
1
;
}
}
else
{
if
(
JAR_XM_LINEAR_INTERPOLATION
)
{
v
=
u
;
u
=
(
b
==
1
||
b
-
2
<=
ch
->
sample
->
loop_start
)
?
ch
->
sample
->
data
[
a
]
:
ch
->
sample
->
data
[
b
-
2
];
}
if
(
ch
->
sample_position
<=
ch
->
sample
->
loop_start
)
{
ch
->
ping
=
true
;
ch
->
sample_position
=
(
ch
->
sample
->
loop_start
<<
1
)
-
ch
->
sample_position
;
}
/* sanity checking */
if
(
ch
->
sample_position
<=
.
0
f
)
{
ch
->
ping
=
true
;
ch
->
sample_position
=
.
0
f
;
}
}
break
;
default:
v
=
.
0
f
;
break
;
}
float
endval
=
JAR_XM_LINEAR_INTERPOLATION
?
jar_xm_LERP
(
u
,
v
,
t
)
:
u
;
#if JAR_XM_RAMPING
if
(
ch
->
frame_count
<
jar_xm_SAMPLE_RAMPING_POINTS
)
{
/* Smoothly transition between old and new sample. */
return
jar_xm_LERP
(
ch
->
end_of_previous_sample
[
ch
->
frame_count
],
endval
,
(
float
)
ch
->
frame_count
/
(
float
)
jar_xm_SAMPLE_RAMPING_POINTS
);
}
#endif
return
endval
;
}
static
void
jar_xm_sample
(
jar_xm_context_t
*
ctx
,
float
*
left
,
float
*
right
)
{
if
(
ctx
->
remaining_samples_in_tick
<=
0
)
{
jar_xm_tick
(
ctx
);
}
ctx
->
remaining_samples_in_tick
--
;
*
left
=
0
.
f
;
*
right
=
0
.
f
;
if
(
ctx
->
max_loop_count
>
0
&&
ctx
->
loop_count
>=
ctx
->
max_loop_count
)
{
return
;
}
for
(
uint8_t
i
=
0
;
i
<
ctx
->
module
.
num_channels
;
++
i
)
{
jar_xm_channel_context_t
*
ch
=
ctx
->
channels
+
i
;
if
(
ch
->
instrument
==
NULL
||
ch
->
sample
==
NULL
||
ch
->
sample_position
<
0
)
{
continue
;
}
const
float
fval
=
jar_xm_next_of_sample
(
ch
);
if
(
!
ch
->
muted
&&
!
ch
->
instrument
->
muted
)
{
*
left
+=
fval
*
ch
->
actual_volume
*
(
1
.
f
-
ch
->
actual_panning
);
*
right
+=
fval
*
ch
->
actual_volume
*
ch
->
actual_panning
;
}
#if JAR_XM_RAMPING
ch
->
frame_count
++
;
jar_xm_SLIDE_TOWARDS
(
ch
->
actual_volume
,
ch
->
target_volume
,
ctx
->
volume_ramp
);
jar_xm_SLIDE_TOWARDS
(
ch
->
actual_panning
,
ch
->
target_panning
,
ctx
->
panning_ramp
);
#endif
}
const
float
fgvol
=
ctx
->
global_volume
*
ctx
->
amplification
;
*
left
*=
fgvol
;
*
right
*=
fgvol
;
#if JAR_XM_DEBUG
if
(
fabs
(
*
left
)
>
1
||
fabs
(
*
right
)
>
1
)
{
DEBUG
(
"clipping frame: %f %f, this is a bad module or a libxm bug"
,
*
left
,
*
right
);
}
#endif
}
void
jar_xm_generate_samples
(
jar_xm_context_t
*
ctx
,
float
*
output
,
size_t
numsamples
)
{
if
(
ctx
&&
output
)
{
ctx
->
generated_samples
+=
numsamples
;
for
(
size_t
i
=
0
;
i
<
numsamples
;
i
++
)
{
jar_xm_sample
(
ctx
,
output
+
(
2
*
i
),
output
+
(
2
*
i
+
1
));
}
}
}
uint64_t
jar_xm_get_remaining_samples
(
jar_xm_context_t
*
ctx
)
{
uint64_t
total
=
0
;
uint8_t
currentLoopCount
=
jar_xm_get_loop_count
(
ctx
);
jar_xm_set_max_loop_count
(
ctx
,
0
);
while
(
jar_xm_get_loop_count
(
ctx
)
==
currentLoopCount
)
{
total
+=
ctx
->
remaining_samples_in_tick
;
ctx
->
remaining_samples_in_tick
=
0
;
jar_xm_tick
(
ctx
);
}
ctx
->
loop_count
=
currentLoopCount
;
return
total
;
}
//--------------------------------------------
//FILE LOADER - TODO - NEEDS TO BE CLEANED UP
//--------------------------------------------
#undef DEBUG
#define DEBUG(...) do { \
fprintf(stderr, __VA_ARGS__); \
fflush(stderr); \
} while(0)
#define DEBUG_ERR(...) do { \
fprintf(stderr, __VA_ARGS__); \
fflush(stderr); \
} while(0)
#define FATAL(...) do { \
fprintf(stderr, __VA_ARGS__); \
fflush(stderr); \
exit(1); \
} while(0)
#define FATAL_ERR(...) do { \
fprintf(stderr, __VA_ARGS__); \
fflush(stderr); \
exit(1); \
} while(0)
int
jar_xm_create_context_from_file
(
jar_xm_context_t
**
ctx
,
uint32_t
rate
,
const
char
*
filename
)
{
FILE
*
xmf
;
int
size
;
xmf
=
fopen
(
filename
,
"rb"
);
if
(
xmf
==
NULL
)
{
DEBUG_ERR
(
"Could not open input file"
);
*
ctx
=
NULL
;
return
3
;
}
fseek
(
xmf
,
0
,
SEEK_END
);
size
=
ftell
(
xmf
);
rewind
(
xmf
);
if
(
size
==
-
1
)
{
fclose
(
xmf
);
DEBUG_ERR
(
"fseek() failed"
);
*
ctx
=
NULL
;
return
4
;
}
char
*
data
=
malloc
(
size
+
1
);
if
(
fread
(
data
,
1
,
size
,
xmf
)
<
size
)
{
fclose
(
xmf
);
DEBUG_ERR
(
"fread() failed"
);
*
ctx
=
NULL
;
return
5
;
}
fclose
(
xmf
);
switch
(
jar_xm_create_context_safe
(
ctx
,
data
,
size
,
rate
))
{
case
0
:
break
;
case
1
:
DEBUG
(
"could not create context: module is not sane
\n
"
);
*
ctx
=
NULL
;
return
1
;
break
;
case
2
:
FATAL
(
"could not create context: malloc failed
\n
"
);
return
2
;
break
;
default:
FATAL
(
"could not create context: unknown error
\n
"
);
return
6
;
break
;
}
return
0
;
}
#endif//end of JAR_XM_IMPLEMENTATION
//-------------------------------------------------------------------------------
#endif//end of INCLUDE_JAR_XM_H
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment