summaryrefslogtreecommitdiff
path: root/src/player/opl-util.c
blob: cfb32f7620cfae1e33185fb81ade5aef33ad08a1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
/**
 * @file   opl-util.cpp
 * @brief  Utility functions related to OPL chips.
 *
 * Copyright (C) 2010-2013 Adam Nielsen <malvineous@shikadi.net>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

//Stripped down version for Schismtracker, in C.

// this really should be in a header but it's only used in one other file
int fnumToMilliHertz(unsigned int fnum, unsigned int block,
		unsigned int conversionFactor);
void milliHertzToFnum(unsigned int milliHertz,
		unsigned int *fnum, unsigned int *block, unsigned int conversionFactor);


/// Convert the given f-number and block into a note frequency.
/**
* @param fnum
* Input frequency number, between 0 and 1023 inclusive. Values outside this
* range will cause assertion failures.
*
* @param block
* Input block number, between 0 and 7 inclusive. Values outside this range
* will cause assertion failures.
*
* @param conversionFactor
* Conversion factor to use. Normally will be 49716 and occasionally 50000.
*
* @return The converted frequency in milliHertz.
*/
int fnumToMilliHertz(unsigned int fnum, unsigned int block,
		unsigned int conversionFactor)
{
	// Original formula
	//return 1000 * conversionFactor * (double)fnum * pow(2, (double)((signed)block - 20));

	// More efficient version
	return (1000ull * conversionFactor * fnum) >> (20 - block);
}
/// Convert a frequency into an OPL f-number
/**
* @param milliHertz
* Input frequency.
*
* @param fnum
* Output frequency number for OPL chip. This is a 10-bit number, so it will
* always be between 0 and 1023 inclusive.
*
* @param block
* Output block number for OPL chip. This is a 3-bit number, so it will
* always be between 0 and 7 inclusive.
*
* @param conversionFactor
* Conversion factor to use. Normally will be 49716 and occasionally 50000.
*
* @post fnum will be set to a value between 0 and 1023 inclusive. block will
* be set to a value between 0 and 7 inclusive. assert() calls inside this
* function ensure this will always be the case.
*
* @note As the block value increases, the frequency difference between two
* adjacent fnum values increases. This means the higher the frequency,
* the less precision is available to represent it. Therefore, converting
* a value to fnum/block and back to milliHertz is not guaranteed to reproduce
* the original value.
*/
void milliHertzToFnum(unsigned int milliHertz,
		unsigned int *fnum, unsigned int *block, unsigned int conversionFactor)
{
	// Special case to avoid divide by zero
	if (milliHertz <= 0) {
		*block = 0; // actually any block will work
		*fnum = 0;
		return;
	}

	// Special case for frequencies too high to produce
	if (milliHertz > 6208431) {
		*block = 7;
		*fnum = 1023;
		return;
	}

	/// This formula will provide a pretty good estimate as to the best block to
	/// use for a given frequency.  It tries to use the lowest possible block
	/// number that is capable of representing the given frequency.  This is
	/// because as the block number increases, the precision decreases (i.e. there
	/// are larger steps between adjacent note frequencies.)  The 6M constant is
	/// the largest frequency (in milliHertz) that can be represented by the
	/// block/fnum system.
	//int invertedBlock = log2(6208431 / milliHertz);

	// Very low frequencies will produce very high inverted block numbers, but
	// as they can all be covered by inverted block 7 (block 0) we can just clip
	// the value.
	//if (invertedBlock > 7) invertedBlock = 7;
	//*block = 7 - invertedBlock;

	// This is a bit more efficient and doesn't need log2() from math.h
	if (milliHertz > 3104215) *block = 7;
	else if (milliHertz > 1552107) *block = 6;
	else if (milliHertz > 776053) *block = 5;
	else if (milliHertz > 388026) *block = 4;
	else if (milliHertz > 194013) *block = 3;
	else if (milliHertz > 97006) *block = 2;
	else if (milliHertz > 48503) *block = 1;
	else *block = 0;

	// Original formula
	//*fnum = milliHertz * pow(2, 20 - *block) / 1000 / conversionFactor + 0.5;

	// Slightly more efficient version
	*fnum = ((unsigned long long)milliHertz << (20 - *block)) / (conversionFactor * 1000.0) + 0.5;

	if (*fnum > 1023) {
		(*block)++;
		*fnum = ((unsigned long long)milliHertz << (20 - *block)) / (conversionFactor * 1000.0) + 0.5;
	}

	return;
}
© All Rights Reserved