contract ArpeggiStudioGenesis is
    ERC721,
    Ownable
{
    ////////////////// STRUCT //////////////////

    /**
     * @dev Struct for Pass and Song
     */
    struct Pass {
	    string title;
        string artist;
	    bytes composition;
        uint mintTime;
        address artistAddress;
    }

    ////////////////// CONSTANTS //////////////////

    uint256 public constant MAX_PASSES = 600;

    uint256 public constant PRESALE_MINT_PRICE = 0.25 ether;
    uint256 public constant GENERAL_MINT_PRICE = 0.3 ether;

    /**
     * @dev Max number of Passes that the contract owner can mint.
     */
    uint256 public constant CREATORS_NUM_PASSES = 64;

    /**
     * @dev Max number of Passes that the contract owner can mint.
     */
    uint256 public constant DEVELOPERS_NUM_PASSES = 24;

    ////////////////// STORAGE //////////////////

    uint256 public _numMinted = 0;

    /**
     * @dev Custom pause functionality for Creator/Developer Reserve Mint
     */
    bool private _paused = true;

    bool public _onlyEarlyAccess = true;

    address public immutable CREATORS_RESERVE;
    address public immutable DEVELOPERS_RESERVE;

    /**
     * @dev Base URL for external_url metadata field.
     */
    string private _basePassExternalUrl = "http://www.arpeggi.io/metadata/pass/";

    /**
     * @dev Base URL for external_url metadata field.
     */
    string private _baseSongExternalUrl = "http://www.arpeggi.io/metadata/song/";

    /**
     * @dev All existing Passes.
     */
    mapping(uint256 => Pass) public _passes;

    /**
     * @dev Addresses with early minting access.
     */
    mapping(address => bool) private _earlyAccess;

    /**
     * @dev For setting scripts. When locked it is irreversible.
     */
    bool private _locked = false;

    /**
     * @dev The scripts used to render a composition from a token
     */
    mapping (uint256 => string) public _scripts;
    uint256 public scriptCount = 0;

    /**
     * @dev The samples used to play a song
     */
    mapping (uint256 => string) public _samples;
    uint256 public sampleCount = 0;

    ////////////////// CONSTRUCTOR //////////////////

    constructor(
        address creatorsReserve,
        address developersReserve
    )
        ERC721(
            "Arpeggi Genesis Studio Pass",
            "ARPEGGI"
        )
    {
        CREATORS_RESERVE = creatorsReserve;
        DEVELOPERS_RESERVE = developersReserve;
    }

    ////////////////// MODIFIERS //////////////////

    /**
     * @dev Modifier to make a function callable only when the contract is not paused.
     * Contract must not be paused
     */
    modifier whenNotPaused() {
        require(!isPaused(), "Paused");
        _;
    }

    /**
     * @dev Modifier to make a function callable only when the contract is paused.
     * Contract must be paused.
     */
    modifier whenPaused() {
        require(isPaused(), "Paused");
        _;
    }

    /**
     * @dev Lock functionality to lock scripts/samples after uploading
     */
    modifier onlyUnlocked() {
        require(!_locked, "Contract is locked");
        _;
    }

    ////////////////// EXTERNAL ONLY-OWNER FUNCTIONS //////////////////

    /**
     * @dev Returns to unpaused state. The contract must be paused.
     */
    function unpause()
        external
        onlyOwner
        whenPaused
    {
        _paused = false;
    }

    /**
     * @dev Function to lock on-chain scripts and samples
     */
    function setLocked()
        external
        onlyOwner
        onlyUnlocked
    {
        _locked = true;
    }

    /**
     * @dev Add a new section of the script
     * @param script String value of script or pointer
     */
    function addScript(
        string memory script
    )
        external
        onlyOwner
        onlyUnlocked
    {
        _scripts[scriptCount] = script;
        scriptCount++;
    }

    /**
     * @dev Overwrite a script section at a particular index
     * @param script String value of script or pointer
     * @param index Index of the script to replace
     */
    function updateScript(
        string memory script,
        uint256 index
    )
        external
        onlyOwner
        onlyUnlocked
    {
        require(index < scriptCount, "Index out of bounds");
        _scripts[index] = script;
    }

    /**
     * @dev Reset script index to zero, caller must be owner and the contract unlocked
     */
    function resetScriptCount()
        external
        onlyOwner
        onlyUnlocked
    {
        scriptCount = 0;
    }

    /**
     * @dev Add a new section of the sample
     * @param sample String value of sample or pointer
     */
    function addSample(
        string memory sample
    )
        external
        onlyOwner
        onlyUnlocked
    {
        _samples[sampleCount] = sample;
        sampleCount++;
    }

    /**
     * @dev Reset sample index to zero, caller must be owner and the contract unlocked
     */
    function resetSampleCount()
        external
        onlyOwner
        onlyUnlocked
    {
        sampleCount = 0;
    }

    /**
     * @dev Set the _onlyEarlyAccess flag.
     */
    function setOnlyEarlyAccess(
        bool to
    )
        external
        onlyOwner
    {
        _onlyEarlyAccess = to;
    }

    /**
     * @dev Set early-access granting or revocation for the addresses. eg. ["0x12345"]
     */
    function setEarlyAccessGrants(
        address[] calldata addresses,
        bool hasAccess
    )
        external
        onlyOwner
    {
        for (uint i = 0; i < addresses.length; i++) {
            _earlyAccess[addresses[i]] = hasAccess;
        }
    }

    /**
     * @dev Allocate passes to Arpeggi Creators Reserve wallet
     */
    function mintCreatorsReserve()
        external
        onlyOwner
        whenPaused
    {
        for (uint i=0; i < CREATORS_NUM_PASSES; i++) {
            _mintToken(CREATORS_RESERVE);
        }
    }

    /**
     * @dev Allocate passes to Arpeggi Creators Reserve wallet
     */
    function mintDevelopersReserve()
        external
        onlyOwner
        whenPaused
    {
        for (uint i=0; i < DEVELOPERS_NUM_PASSES; i++) {
            _mintToken(DEVELOPERS_RESERVE);
        }
    }


    /**
     * @dev Withdraw funds from contract to owner
     */
    function withdraw() public onlyOwner {
        payable(owner()).transfer(address(this).balance);
    }

    ////////////////// EXTERNAL FUNCTIONS //////////////////

    /**
     * @dev Mint a lab pass
     */
    function mint()
        external
        payable
        whenNotPaused
    {
        require(_numMinted < MAX_PASSES, "No More Passes Left");
        require(
            (_onlyEarlyAccess && PRESALE_MINT_PRICE == msg.value) || (!_onlyEarlyAccess && GENERAL_MINT_PRICE == msg.value),
            "Incorrect Ether value.");
        require(!_isContract(msg.sender), "Can't mint from a contract smh");
        require(
            !_onlyEarlyAccess || _earlyAccess[msg.sender],
            "You are not approved for presale"
        );

        _mintToken(msg.sender);

        if (_onlyEarlyAccess) {
            _earlyAccess[msg.sender] = false;
        }
    }

    function pressSong(
        uint256 _tokenId,
        bytes calldata _composition,
        string calldata _title,
        string calldata _artist
    )
        external
        whenNotPaused
    {
        require(_isApprovedOrOwner(_msgSender(), _tokenId));
        require(_passes[_tokenId].mintTime == 0, "Your Studio Pass has already been used");

        _pressSongToken(msg.sender, _tokenId, _title, _artist, _composition);
    }

    ////////////////// PUBLIC ONLY-OWNER FUNCTIONS //////////////////

    /**
     * @dev Set the base URL for external_url metadata field.
     */
    function setBasePassExternalUrl(
        string memory url
    )
        public
        onlyOwner
    {
        _basePassExternalUrl = url;
    }

    /**
     * @dev Set the base URL for external_url metadata field.
     */
    function setBaseSongExternalUrl(
        string memory url
    )
        public
        onlyOwner
    {
        _baseSongExternalUrl = url;
    }

    ////////////////// PUBLIC FUNCTIONS //////////////////

    /**
     * @dev Returns data URI of token metadata.
     */
    function tokenURI(
        uint256 _tokenId
    )
        public
        override
        view
        returns (string memory)
    {
        if (_passes[_tokenId].mintTime == 0) {
            return string(abi.encodePacked(_basePassExternalUrl, Strings.toString(_tokenId)));
        }
        else {
            return string(abi.encodePacked(_baseSongExternalUrl, Strings.toString(_tokenId)));
        }
    }


    /**
     * @dev Checks an address to see if it has early access
     */
    function hasEarlyAccess(address _address) public view returns (bool) {
        return _earlyAccess[_address] == true;
    }

    /**
     * @dev Returns true if the contract is paused, and false otherwise.
     */
    function isPaused()
        public
        view
        returns (bool)
    {
        return _paused;
    }

    ////////////////// INTERNAL FUNCTIONS //////////////////

    function _mintToken(
        address _to
    )
        internal
    {

        _numMinted++;
        _safeMint(_to, _numMinted);
    }

    function _pressSongToken(
        address _artistAddress,
        uint256 _tokenId,
        string calldata _title,
        string calldata _artist,
        bytes calldata _composition
    )
        internal
        returns(uint256 _songToken)
    {
        _passes[_tokenId].title = _title;
        _passes[_tokenId].artist = _artist;
        _passes[_tokenId].composition = _composition;
        _passes[_tokenId].mintTime = block.timestamp;
        _passes[_tokenId].artistAddress = _artistAddress;
        return _tokenId;
    }

    /**
     * @dev Function to check if an address is a contract for minting
     */
    function _isContract(
        address _addr
    )
        internal
        view
        returns (bool)
    {
		uint32 _size;
		assembly {
			_size:= extcodesize(_addr)
		}
		return (_size > 0);
	}
}
// Arpeggi Studio Genesis is the first on-chain music creation platform. All data and instructions to recreate your Arpeggi song reside on the Ethereum blockchain.
//
// To recreate your song, you will need to retrieve two compressed files from the chain: "samples" and "scripts".
//
//  STEPS TO RETRIEVE "samples":
// - The mp3 samples to play your song have been packaged together as a single compressed file, split into pieces, and stored in transaction data on-chain.
// - Make a new file on your local, 'samples.hex,' to piece together the samples hex data gathered from the contract.
// - Query the contract for the sampleCount - this is the number of pieces of the samples file. You will need all of them.
// - Pass in the sampleCount starting with 0 into the _samples read method of the ArpeggiStudioGenesis contract, this is will return a transaction hash.
// - Use the transaction hash to find the sample transaction on Etherscan.
// - Copy the "Input Data" field of this the transaction to the end of your local 'samples.hex' file, removing the leading 0x at the beggining of the input data. 
// - Repeat the last three steps, incrementing the sampleConut in the _samples read method and appending to the end of your local file data until you have copied all the sample data.
// - Convert the local file from hex to binary using a command line tool. Example command: xxd -r -p samples.hex > samples.zip
// - Follow the instructions to retrieve the scripts below.
//
//  STEPS TO RETRIEVE "scripts":
// - The song player script is written in JavaScript, split into pieces, and stored on chain.
// - Make a new file on your local, 'scripts.hex' to piece together the script hex data gathered from the contract
// - Query the contract for the scriptCount - this is the number of pieces of the player script. It's likely to be one transaction.
// - Pass in the scriptCount starting with 0 into the _scripts read method of the ArpeggiStudioGenesis contract, this is will return a transaction hash.
// - Use the transaction hash to find the script transaction on Etherscan
// - Copy the "Input Data" field of this the transaction to your local 'scripts.hex' file, removing the leading 0x at the beggining of the input data. 
// - Repeat these last three steps, incrementing the scriptCount in the _scripts method and appending to the end of your local file data until you have copied all the script data.
// - Convert the local file from hex to binary using a command line tool. Example command: xxd -r -p scripts.hex > scripts.zip
// 
//  PLAYING YOUR SONG:
// - Extract the scripts and the samples files. This is all of the data required to play your song.
// - Follow the instructions in the scripts/README file to play your song!
function addSample(
    string memory sample
)
    external
    onlyOwner
    onlyUnlocked
{
    _samples[sampleCount] = sample;
    sampleCount++;
}

/**
 * @dev Add a new section of the sample
 * @param sample String value of sample or pointer
 */